search icon

Installing Arch Linux on a laptop

· updated · 24 min read
Arch Linux
Pingu the archer shooting at a laptop

Introduction

After deciding to replace Windows with a more privacy-friendly OS, and reading about different Linux distributions, I opted for the Plasma version of Manjaro Linux for a few reasons:

After two years of familiarizing myself with the Linux ecosystem, terminal commands, and possible customization, I felt ready to remove the middle man that was Manjaro and go straight for Arch Linux.

In this guide, I explain how I installed Arch Linux on my laptop with these features:

  • Plasma 6: the Breeze theme is great and consistent everywhere
  • Plymouth: the boot process should also look nice
  • Wayland: a modern display server alternative to X11
  • PipeWire: a modern audio management alternative to PulseAudio and JACK
  • LVM: multiple virtual partitions inside a single encrypted partition
  • Full-disk encryption: preventing thieves from accessing data
  • TPM2 PIN decryption: avoid entering a long passphrase at every boot
  • Unified Kernel Image: the system boots from a single file
  • Secure Boot: prevent booting from unauthorized drives

Most of these steps can be found in the Arch Wiki, even if they span several pages. I aim to make the process as straightforward as possible, even for a novice.

Requirements

  • A modern laptop with an SSD and TPM2
  • A spare USB flash drive
  • Internet connectivity
  • Patience. Lots of it.

Before rebooting

Download the official installation ISO using a BitTorrent client like qBittorrent.

Mount the ISO to a USB flash drive, or copy it to a Ventoy flash drive.

Please make a backup of important files in the SSD, as it will be formatted.

Modifying UEFI settings

Before booting from the installation medium, let’s configure a couple of UEFI settings.

First, ensure you have a UEFI admin password; otherwise, Secure Boot can be disabled by anyone.

Then, enable the Secure Boot setup mode. With this, we can replace the Secure Boot keys later from the OS.

Since instructions vary for each manufacturer, you need to find a guide for your device.

Booting from ArchISO

Next, we can plug in the installation drive and boot from the ISO file. After a few seconds, you should see the following command line interface:

The CLI interface of ArchISO
The CLI interface of ArchISO

First, set the keyboard layout, time zone, and silence the PC speaker:

# load the italian keyboard layout
loadkeys it
 
# silence the speaker
rmmod pcspkr
 
# enlarge the font
setfont ter-128b
 
# set the correct time
timedatectl set-timezone "Europe/Rome"
timedatectl set-ntp true

Wi-Fi setup

If you don’t have an Ethernet cable nearby, set up wi-fi connectivity:

iwctl

From the iwctl prompt, run device list. You should see the following:

                                   Devices                                   *  
--------------------------------------------------------------------------------  
 Name                  Address               Powered     Adapter     Mode         
--------------------------------------------------------------------------------  
 wlan0                 7c:76:35:83:65:43     on          phy0        station        

If the device is off, activate it with

device wlan0 set-property Powered on

Then, connect to your network and exit iwctl:

station wlan0 connect "My SSID"
exit

From the shell prompt, check for connectivity:

ping -c 3 archlinux.org

Preparing the SSD

Now we will format and organize the disk into several partitions. We will then initialize the OS and chroot into it.

Ideal sector size

For optimal performance, set the ideal sector size of the SSD.

For SATA Check the available sector sizes:
hdparm -I /dev/sda | grep 'Sector size:'
       Logical  Sector size:                   512 bytes  
       Physical Sector size:                  4096 bytes

If sector size 4096 is suppoted, select it and confirm the change:

hdparm --set-sector-size 4096 --please-destroy-my-drive /dev/sda
hdparm -I /dev/sda | grep 'Sector size:'
       Logical  Sector size:                  4096 bytes  
       Physical Sector size:                  4096 bytes
For NVMe Check the performance of different LBA sizes:
nvme id-ns -H /dev/nvme0n1 | grep "Relative Performance"
LBA Format  0 : Metadata Size: 0   bytes - Data Size: 512 bytes - Relative Performance: 0x2 Good (in use)
LBA Format  1 : Metadata Size: 0   bytes - Data Size: 4096 bytes - Relative Performance: 0x1 Better

Select the best LBA format (1 in this case) and confirm the change:

nvme format --lbaf=1 /dev/nvme0n1
nvme id-ns -H /dev/nvme0n1 | grep "Relative Performance"
LBA Format  0 : Metadata Size: 0   bytes - Data Size: 512 bytes - Relative Performance: 0x2 Good
LBA Format  1 : Metadata Size: 0   bytes - Data Size: 4096 bytes - Relative Performance: 0x1 Better (in use)

From now on, I will assume the SSD is /dev/nvme0n1.

Boot partition

Create a 1GB boot partition to store the bootloader:

# create a new partition table
parted -s /dev/nvme0n1 mklabel gpt
 
# create a boot partition
parted -s /dev/nvme0n1 mkpart "ESP" fat32 1MiB 1025MiB
 
# make the partition bootable
parted -s /dev/nvme0n1 set 1 esp on

Encrypted root partition

Create a large OS partition, and encrypt it using a strong passphrase when prompted:

# create a linux partition with the remaining space
parted -s /dev/nvme0n1 mkpart "OS" ext4 1025MiB 100%
 
# make the new partition a LUKS volume
cryptsetup luksFormat /dev/nvme0n1p2 \
 --cipher aes-xts-plain64 \
 --key-size 512 \
 --hash sha512 \
 --pbkdf argon2id \
 --pbkdf-memory 1048576 \
 --pbkdf-parallel 4 \
 --iter-time 5000 \
 --sector-size 4096

Why these encryption options?
AES-XTS is the fastest cipher, you can test it by running cryptsetup benchmark. XTS mode will split the key in half, so specifying a 512 key size means using two AES 256 keys. Argon2ID replaced Argon2I only recently, and we are requiring 1GB of RAM, 4 core and 5 seconds of computational time to make brute-force attacks impractical. The optimal sector size is the one we set before for the SSD.

Decrypt the LUKS volume:

cryptsetup open /dev/nvme0n1p2 "cryptpart"

The content of the volume is now available at /dev/mapper/cryptpart.

LVM volumes

In our case, LVM makes it easy to allocate virtual partitions inside an encrypted SSD partition.

Why not use the LUKS volume directly as a single partition?
For me, it’s convenient to keep my files inside a “home” partition that stays separate from the “system” one, as I can repair the system partition without affecting my personal files. But I also lived without LVM for years, so it’s not a necessity.

Using LVM terminology, our decrypted LUKS volume will be used as a “physical volume” (PV). We will add this PV to a “volume group” (VG). Then, we will create different “logical volumes” (LV) that will be allocated to the VG.

# create the physical volume
pvcreate /dev/mapper/cryptpart
 
# create the volume group and assign the pv to it
vgcreate "os" /dev/mapper/cryptpart
 
# create a 25 GB logical volume for backup purpose (optional)
lvcreate -L 25GB "os" -n backup
 
# create a 100 GB logical volume for system files 
lvcreate -L 100GB "os" -n root
 
# create a volume with the remaining space for user files
lvcreate -l 100%FREE "os" -n home
lvreduce -L -256M os/home

You can now check these groups by running:

lvdisplay
lsblk

The output should look like this:

NAME              MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS  
nvme0n1           259:0    0 465.8G  0 disk     
  nvme0n1p1       259:1    0     1G  0 part  
  nvme0n1p2       259:2    0 464.8G  0 part    
   cryptpart      254:0    0 464.7G  0 crypt    
     os-backup    254:1    0    25G  0 lvm     
     os-root      254:2    0   200G  0 lvm     
     os-home      254:3    0 239.5G  0 lvm   

File systems

Now that the logical volumes are in place, we can build the file system for them and for the boot partition:

# logical volumes
mkfs.ext4 /dev/os/backup
mkfs.ext4 /dev/os/root
mkfs.ext4 /dev/os/home
 
# boot partition
mkfs.fat -F32 /dev/nvme0n1p1

Mounting

The last step is to mount these file systems into folders, so we can place our new OS files inside:

# logical volumes
mount --mkdir /dev/os/root   /mnt
mount --mkdir /dev/os/backup /mnt/backup
mount --mkdir /dev/os/home   /mnt/home
mkdir /mnt/boot
 
# boot partition
mount --mkdir /dev/nvme0n1p1 /mnt/efi

Chrooting

The new system has its root folder “/” mounted to /mnt. We can initialize it by installing some packages:

pacstrap "/mnt" base linux linux-firmware "intel-ucode" nano networkmanager jq lvm2

If you have an AMD processor, replace intel-ucode with amd-ucode.

Once done, save the current disk configuration to the root volume:

genfstab -U /mnt >> /mnt/etc/fstab

Now, let’s enter the new Arch Linux system as if we booted from it:

arch-chroot /mnt

Command line interface

I highly recommend taking some time now to follow the steps I wrote in this post, as it will greatly improve the user experience for the rest of this guide.

In particular, I talk about setting up the correct language and timezone, create users, and get yay, a command line utility to install packages from the AUR.

Graphical user interface

Once the CLI is set up, installing the GUI is a breeze.

How KDE Plasma 6 will look
How KDE Plasma 6 will look

Display server

Let’s install some packages needed to run graphical applications:

pacman -S wayland qt6-wayland qt6-virtualkeyboard sddm xorg-xeyes xorg-xwayland xdg-desktop-portal wl-clipboard glfw-wayland

Set the default cursor to match the Breeze theme:

nano /usr/share/icons/default/index.theme

And replace “Awaita” with “Breeze”:

index.theme
[Icon Theme]  
Inherits=Awaita
Inherits=Breeze

Desktop environment

Now we can install the desktop environment (Plasma) and session manager (SDDM):

pacman -S plasma-meta kde-gtk-config sddm xorg-xinit xorg-xhost phonon-qt6-vlc qt6-multimedia-ffmpeg dnsmasq kdenetwork-filesharing usbutils

We need to configure SDDM to launch Plasma using Wayland:

nano /etc/sddm.conf.d/10-wayland.conf

Copy the following into the file:

10-wayland.conf
[General]
DisplayServer=wayland
GreeterEnvironment=QT_WAYLAND_SHELL_INTEGRATION=layer-shell
 
[Wayland]
CompositorCommand=kwin_wayland --drm --no-lockscreen --no-global-shortcuts --inputmethod qtvirtualkeyboard
 
[Theme]
EnableAvatars=true
DisableAvatarsThreshold=7

Once we apply this configuration, we can start SDDM on each boot:

systemctl enable sddm
How SDDM will look
How SDDM will look

Graphical boot

With the current setup, the boot process would display text in a terminal until we see SDDM’s login screen. By using a Plymouth theme, we can hide this behind a nicer loading screen:

How Plymouth with the Breeze theme will look
How Plymouth with the Breeze theme will look

Install Plymouth and set the Breeze theme:

pacman -S plymouth breeze-plymouth
plymouth-set-default-theme breeze

Now that the graphical part is complete, we can tackle the most delicate part: configuring the boot process.

Boot process

There are several components to be configured for the boot process to work smoothly and securely.

The final result will be a single file placed in the unencrypted boot partition, signed with a Secure Boot “db” key. When Secure Boot is on, the laptop can only boot from this file.

Kernel command line

First, we need to configure the Linux kernel to find our root volume and display Plymouth properly.

Run blkid and save the UUID of the encrypted SSD partition, which has type="crypto_LUKS":

/dev/nvme0n1p1: UUID="9EAC-DCD1" BLOCK_SIZE="4096" TYPE="vfat" PARTLABEL="ESP" PARTUUID="ded4c683-b672-45af-9bee-10887c030b71"  
/dev/nvme0n1p2: UUID="2e708dd6-61e4-40ec-b30e-ca119d4467e5" TYPE="crypto_LUKS" PARTLABEL="OS" PARTUUID="6e6e282a-4ad7-4c9b-8fff-22f353b9c552"  
/dev/mapper/cryptpart: UUID="wWfAjv-lv5u-Mbyo-iAZT-K8He-6nIc-TNGpfP" TYPE="LVM2_member"
/dev/mapper/os-backup: UUID="9c4f9b51-f68e-4d4f-bbd1-f32948ba7018" BLOCK_SIZE="4096" TYPE="ext4"
/dev/mapper/os-home: UUID="c5391d08-7901-4296-bf0a-a7f7c94e44dc" BLOCK_SIZE="4096" TYPE="ext4"
/dev/mapper/os-root: UUID="f52ad892-4f7a-4377-9e70-e6890b553760" BLOCK_SIZE="4096" TYPE="ext4"

Create a file with the kernel parameters:

mkdir -p /etc/cmdline.d
nano /etc/cmdline.d/custom.conf

Copy the following into the file:

custom.conf
# encrypted partition uuid, name, and options
rd.luks.name=2e708dd6-61e4-40ec-b30e-ca119d4467e5=cryptpart
rd.luks.options=discard,no-read-workqueue,no-write-workqueue
 
# root directory
root=/dev/os/root  
rw
rootwait
 
# plymouth
quiet
splash
vt.global_cursor_default=0
fbcon=nodefer
 
# prevent launching privileged commands from the boot shell
rd.shell=0
rd.emergency=reboot

Temporary file system

We will now configure the generation of the boot temporary file system, a file that contains the Linux kernel and other boot utilities.

nano /etc/mkinitcpio.conf

To use the TPM and early graphical boot, comment out the existing MODULES array, make a copy, and add tpm_tis and your GPU modules to it. In this case, i915 is the module for the integrated Intel GPU:

mkinitcpio.conf
#MODULES=()
MODULES=(tpm_tis i915)
 
# ...

In the same file, make a copy of the HOOKS array and modify it in this way:

  • replace the base and udev hooks with systemd
  • move keyboard after systemd
  • add plymouth after kms
  • replace keymap and consolefont with sd-vconsole
  • add sd-encrypt and lvm2 after block

The final result should look like this:

mkinitcpio.conf
# ...
 
#HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck)
HOOKS=(systemd keyboard autodetect microcode modconf kms plymouth sd-vconsole block sd-encrypt lvm2 filesystems fsck)
 
# ...

Secure Boot

By default, most computers authorize only Microsoft’s signature, so that only the official Windows bootloader can be executed at boot.

Now, we will create and enroll our own keys as UEFI variables. We will then use the db key to sign our bootloader automatically every time it gets rebuilt.

Install these utilities and generate your Secure Boot keys:

pacman -S efibootmgr sbctl sbsigntools
sbctl create-keys

Check if the system has been booted into Setup Mode before enrolling your Secure Boot keys:

sbctl status

You should see:

Installed:      ✓ sbctl is installed
Owner GUID:     ge708dd6-61e4-40ec-b30e-ca119d4467e5
Setup Mode:     ✓ Enabled
Secure Boot:    ✗ Disabled
Vendor Keys:    microsoft

Then, you can enroll your keys:

sbctl enroll-keys -m

Create a new hook at /etc/initcpio/post/sbsign that automatically signs the unified kernel image whenever mkinitcpio is run:

sbsign
#!/usr/bin/env bash
 
uki="$3"
[[ -n "$uki" ]] || exit 0
 
key=/var/lib/sbctl/keys/db/db.key
cert=/var/lib/sbctl/keys/db/db.pem
if ! sbverify --cert "$cert" "$uki" &>/dev/null; then
 sbsign --key "$key" --cert "$cert" --output "$uki" "$uki" &>/dev/null
fi

Make the file executable:

chmod +x /etc/initcpio/post/sbsign

Remove some unneeded sbctl hooks:

rm -f /usr/lib/initcpio/post/sbctl /usr/share/libalpm/hooks/zz-sbctl.hook

Prevent pacman from restoring them by editing /etc/pacman.conf:

pacman.conf
# ...
 
[options]
NoExtract    = usr/lib/initcpio/post/sbctl  
NoExtract    = usr/share/libalpm/hooks/zz-sbctl.hook
 
# ...

Unified Kernel Image

Make a backup copy of /etc/mkinitcpio.d/linux.preset:

mv /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux.preset.bak

Then, create a new .preset file:

nano /etc/mkinitcpio.d/linux.preset

Copy the following into the file:

linux.preset
ALL_kver="/boot/vmlinuz-linux"  
PRESETS=('default' 'fallback')
 
default_uki="/efi/arch.efi"
fallback_uki="/efi/arch-fallback.efi"  
fallback_options="-S autodetect"

The fallback_uki option will create another unified kernel image with different settings, useful in case you have problems booting from the default file.

Now that we have everything in place, we can finally generate and sign the two unified kernel images:

mkinitcpio -P

Check that two files have been created inside the boot partition, and that they are signed with your Secure Boot key:

ls -la /efi
sbverify --cert /var/lib/sbctl/keys/db/db.pem /efi/arch.efi
sbverify --cert /var/lib/sbctl/keys/db/db.pem /efi/arch-fallback.efi

UEFI boot entries

To make the UEFI aware of these files, we need to create two boot entries for these UKIs:

efibootmgr \
 --create \
 --label "Arch Linux" \
 --index 0 \
 --disk /dev/nvme0n1 \
 --part 1 \
 --loader "\\arch.efi"
 
efibootmgr \
 --create \
 --label "Arch Linux Fallback" \
 --index 1 \
 --disk /dev/nvme0n1 \
 --part 1 \
 --loader "\\arch-fallback.efi"

TPM unlock

With the current setup, we would need to enter the LUKS decryption passphrase at every boot. We can use our laptop TPM to protect the LUKS encryption key, and retrieve it after we enter a PIN:

systemd-cryptenroll \
 --tpm2-device=auto \
 --tpm2-pcrs=0+1+5+7+8 \
 --tpm2-with-pin=true \
 /dev/nvme0n1p2

Why this PCRs combination?
The TPM will “take a snapshot” of the current state of the UEFI firmware, UEFI settings, partition table, secure boot keys, and kernel command line. If one of these change after the enrollment, the TPM will consider the environment “altered”, and will not prompt us for the PIN.
In this case, we will be prompted by Plymouth for our LUKS passphrase. We can always re-enroll the TPM after booting, if we trust that no one altered our PC while we were away.

You can check that a new entry has been created under the “Tokens” section of the LUKS header:

cryptsetup luksDump /dev/nvme0n1p2
# ...
 
Tokens:
  0: systemd-tpm2
        tpm2-hash-pcrs:   0+1+5+7+8
        tpm2-pcr-bank:    sha256
        tpm2-pubkey:
                    (null)
        tpm2-pubkey-pcrs: 
        tpm2-primary-alg: ecc
        tpm2-blob:  00 9e 00 20 3c cc 11 f3 44 33 0f 35 5e 37 f3 ee
 
# ...

Starting from the next boot, the PIN prompt will look this:

How the PIN prompt will look
How the PIN prompt will look

Other configurations

Now that the boot process is done, we can finalize our installation with some desktop-specific features.

Audio

To make audio work out of the box, install PipeWire:

pacman -S pipewire pipewire-alsa pipewire-jack pipewire-pulse pipewire-v4l2 wireplumber alsa-utils

Bluetooth

For Bluetooth connectivity, install Bluez:

pacman -S bluez

Fingerprint reader

If your laptop has a fingerprint sensor, consider following the excellent Fprint guide after you boot your laptop.

Printing

CUPS comes with a web interface to manage printers. To install it, run:

pacman -S cups cups-pdf
systemctl enable cups

Power-saving mode

To extend the battery duration, install TLP:

pacman -S tlp tlp-rdw

Then, enable the default settings by running:

systemctl mask systemd-rfkill.service
systemctl mask systemd-rfkill.socket
systemctl enable NetworkManager-dispatcher
systemctl enable tlp

If you need a GUI to change them, I use slimbookbattery from the AUR.

Wi-Fi backend

By default, NetworkManager uses wpa_supplicant as its wireless network backend. To use the newer iwd, create a custom configuration file:

nano /etc/NetworkManager/conf.d/custom.conf

Copy the following into the file:

custom.conf
[main]
rc-manager=resolvconf
 
[device]
wifi.backend=iwd

Start NetworkManager on each boot:

systemctl enable NetworkManager

Graphics card

To keep the guide straightforward, we only covered the example of a laptop with an integrated Intel GPU. However, there are a few configurations to change based on your GPU:

Intel-only Install the user-space driver:
pacman -S mesa mesa-demos mesa-utils vulkan-intel intel-media-driver intel-gpu-tools
Nvidia-only

Install the Nvidia kernel driver:

pacman -S nvidia nvtop

Append to the kernel command line the following:

/etc/cmdline.d/custom.conf
# ...
 
# nvidia gpu
nvidia_drm.modeset=1
nvidia_drm.fbdev=1

In your temporary file system configuration file, replace the Intel module with Nvidia modules:

/etc/mkinitcpio.conf
MODULES=(tpm_tis i915)
MODULES=(tpm_tis nvidia nvidia_modeset nvidia_uvm nvidia_drm)

Create an update hook that will regenerate the UKI every time Nvidia drivers gets updated:

nano /usr/share/libalpm/hooks/nvidia.hook

Copy the following into the file:

nvidia.hook
[Trigger]
Operation=Install
Operation=Upgrade
Operation=Remove
Type=Package
Target=nvidia
Target=usr/lib/modules/*/vmlinuz
 
[Action]
Description=Update NVIDIA module in initcpio
Depends=mkinitcpio
When=PostTransaction
NeedsTargets
Exec=/bin/sh -c 'while read -r trg; do case $trg in linux*) exit 0; esac; done; /usr/bin/mkinitcpio -P'

Regenerate the UKI:

mkinitcpio -P
AMD-only

Install the user-space drivers:

pacman -S mesa mesa-vdpau libva-mesa-driver vulkan-radeon radeontop

In your temporary file system configuration file, remove the Intel module:

/etc/mkinitcpio.conf
MODULES=(tpm_tis i915)
MODULES=(tpm_tis)

Regenerate the UKI:

mkinitcpio -P
Intel + Nvidia laptop Install both Intel user-space and Nvidia kernel drivers:
pacman -S mesa mesa-demos mesa-utils vulkan-intel vulkan-mesa-layers intel-media-driver intel-gpu-tools nvidia nvidia-prime nvtop

To run apps using the dedicated GPU, prepend them with prime-run:

# should run on intel integrated card
glxgears
 
# should run on nvidia dedicated card
prime-run glxgears
Intel + AMD laptop Install both Intel and AMD user-space drivers:
pacman -S mesa mesa-demos mesa-utils vulkan-intel vulkan-mesa-layers intel-media-driver intel-gpu-tools libva-mesa-driver vulkan-radeon radeontop

To run apps using the dedicated GPU, prepend them with DRI_PRIME=1:

# should run on intel integrated card
glxgears
 
# should run on amd dedicated card
DRI_PRIME=1 glxgears

In case of issues, refer to the Graphics category in the Arch Wiki.

Reboot

You can finally reboot and test that everything works well.

To reboot, exit from the chroot and unmount all volumes and partitions.

exit
umount -a /mnt
vgchange -an "os"
cryptsetup luksClose /dev/mapper/cryptpart
shutdown

At the next boot, Plymouth will prompt you for your TPM PIN, followed by SDDM prompting you for your user password.

Once logged in, you can check that the setup mode of UEFI Secure Boot has been disabled:

sbctl status
Installed:      ✓ sbctl is installed
Owner GUID:     ge708dd6-61e4-40ec-b30e-ca119d4467e5
Setup Mode:     ✓ Disabled
Secure Boot:    ✓ Enabled
Vendor Keys:    microsoft

If it’s still enabled, remember to disable it from your UEFI settings.

Happy Arching!

Pingu the archer cheering
Pingu the archer cheering

Did you enjoy the article?

heart icon

Discuss it with others:

Other posts for you

Pingu the archer shooting at a Raspberry Pi
Feels good man facing a Yakuake terminal window