I install Arch Linux maybe once every 3 years. It can be annoying to have to navigate through all the relevant ArchWiki pages to find the pieces of information I need.
So this post lists the instructions I followed when I last installed Arch on a server with:
- Unified Kernel Image
- Secure Boot
- Encrypted rootfs
- TPM2 auto-unlocking
- Firewall
I generally try not to deviate too much from the default settings, so aside from the nftables config, you can expect most things to be configurable by just uncommenting a few lines in some files.
§Preparation
-
Download the Arch Linux ISO and write it to a USB drive:
$ cat arch.iso > /dev/sdz
-
Put Secure Boot in Setup Mode so you can enroll your own keys.
-
Boot from the USB drive.
§Live environment
Formatting the disk and installing the base system.
§Partition the disk
Start fdisk
to partition the disk (replace /dev/sdz
with the actual block
device path):
$ fdisk /dev/sdz
Then create a GPT partition table with the following partitions:
- EFI system partition (512 MiB) (type 1).
- Linux root (x86-64) (type 23).
§Format the partitions
Format the EFI system partition with FAT32:
$ mkfs.fat -F 32 /dev/sdz1
Encrypt the root partition with LUKS:
$ cryptsetup luksFormat /dev/sdz2
Decrypt the root partition:
$ cryptsetup open /dev/sdz2 root
Format the root volume with Btrfs:
$ mkfs.btrfs /dev/mapper/root
§Mount the partitions
Mount the root partition to /mnt
:
$ mount /dev/mapper/root /mnt
Mount the EFI system partition to /mnt/efi
:
$ mount --mkdir /dev/sdz1 /mnt/efi
§Install the base system
Connect to the network (wired + DHCP works out of the box), then bootstrap the system:
$ pacstrap -K /mnt base linux linux-firmware util-linux intel-ucode \
dosfstools btrfs-progs sbctl efibootmgr neovim
Add iwd
if a wireless connection is needed.
§Generate the fstab
Auto-generate the fstab:
$ genfstab -U /mnt >> /mnt/etc/fstab
Edit /mnt/etc/fstab
to replace the mount options with defaults,noatime
.
§Chroot
Change root to the new system:
$ arch-chroot /mnt
§Base config
Configuring localization and hostname.
§Timezone
Set the timezone:
$ ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
Synchronize the hardware clock:
$ hwclock --systohc
§Locale
Uncomment en_US.UTF-8
in /etc/locale.conf
and generate the locales:
$ locale-gen
Generating locales...
en_US.UTF-8... done
Generation complete.
§Keymap
Set the console keymap:
$ echo KEYMAP=us >> /etc/vconsole.conf
§Hostname
Set the hostname:
$ echo whatever >> /etc/hostname
§Configure the boot image
Kernel, cmdline, initramfs, microcode, splash image bundled into a single Unified Kernel Image that can be natively booted by a UEFI firmware, no additional bootloader needed.
§Encrypted rootfs hooks
Edit /etc/mkinitcpio.conf
hooks to support an encrypted rootfs:
HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole sd-encrypt block filesystems fsck)
§Unified Kernel Image (UKI)
Edit /etc/mkinitcpio.d/linux.preset
to build a
UKI:
ALL_kver="/boot/vmlinuz-linux"
PRESETS=('default' 'fallback')
default_uki="/efi/EFI/Linux/arch-linux.efi"
default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"
§dm-crypt discard
Edit /etc/kernel/cmdline
to allow LUKS discards:
rd.luks.options=discard
There are security implications.
§Setup Secure Boot
Create the Secure Boot keys:
$ sbctl create-keys
Created Owner UUID bfacc526-9b2b-4752-a8b2-add202b7b6c7
Creating secure boot keys...✓
Secure boot keys created!
Enroll them:
$ sbctl enroll-keys -m
Enrolling keys to EFI variables...
With vendor keys from microsoft...✓
Enrolled keys to the EFI variables!
§Build the boot images
Create the EFI/Linux
directory:
$ mkdir -p /efi/EFI/Linux
Build the boot images:
$ mkinitcpio -P
Verify that the images are properly signed (done automatically by the
/usr/lib/initcpio/post/sbctl
hook):
$ sbctl verify
Verifying file database and EFI images in /efi...
✓ /efi/EFI/Linux/arch-linux-fallback.efi is signed
✓ /efi/EFI/Linux/arch-linux.efi is signed
§Create the boot entries
Create the main boot entry (replace /dev/sdz
and 42
with the actual
values):
$ efibootmgr --create --disk /dev/sdz --part 42 --label "Arch Linux" \
--loader '\EFI\Linux\arch-linux.efi' --unicode
Create the fallback boot entry:
$ efibootmgr --create --disk /dev/sdz --part 42 --label "Arch Linux (fallback)" \
--loader '\EFI\Linux\arch-linux-fallback.efi' --unicode
You can also delete existing entries like the Windows Boot Manager.
§Reboot
Set the root password:
$ passwd
Reboot the system:
$ reboot
§First boot
If the system booted successfully, at least Secure Boot is working.
§TMP2 unlocking
Install tpm2-tss
:
$ pacman -S tmp2-tss
Enroll a new key with TPM2 unlocking:
$ systemd-cryptenroll --tpm2-device=auto /dev/sdz2
By default, the key is bound to the Secure Boot state (PCR 7), which has security implications. See Systemd-cryptenroll: Trusted Platform Module for alternatives.
§Setup networking
Configure DHCP for the wired interfaces:
[Match]
Name=en*
[Network]
DHCP=ipv4
[DHCP]
RouteMetric=10
UseDNS=no
Link /etc/resolv.conf
to the systemd-resolved stub:
$ ln -sf /usr/lib/systemd/resolv.conf /etc/resolv.conf
Enable the network services:
$ systemctl enable --now systemd-networkd systemd-resolved systemd-timesyncd
§Configure the system services
Install irqbalance
if you believe in evenly distributed IRQs:
$ pacman -S irqbalance
Set a journal size limit:
[Journal]
SystemMaxUse=100M
Configure system watchdogs:
[Manager]
RuntimeWatchdogSec=15s
RebootWatchdogSec=3min
Enable system services:
$ systemctl enable --now fstrim.timer irqbalance systemd-oomd
§Setup OpenSSH
Install the package:
$ pacman -S openssh
Configure public key authentication (permissions are important):
$ mkdir -m 700 ~/.ssh
$ echo 'ssh-ed25519 AAAA...' >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
Enable the server:
$ systemctl enable --now sshd
§Setup a firewall
Install iptables-nft
:
$ pacman -S iptables-nft
Allow core network services and SSH:
#!/usr/bin/nft -f
destroy table inet firewall
table inet firewall {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iifname lo accept
icmp type echo-request accept
icmpv6 type { echo-request, nd-router-advert, nd-router-solicit, nd-neighbor-advert, nd-neighbor-solicit } accept
tcp dport llmnr accept
udp dport llmnr accept
tcp dport ssh accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
}
Enable the firewall:
$ systemctl enable --now nftables
§Next steps
- Reboot to check that everything works.
- Install Tailscale, k3s, whatever.
- Profit.