Arch Linux server quick install guide

7 min read

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

  1. Download the Arch Linux ISO and write it to a USB drive:

    $ cat arch.iso > /dev/sdz
    
  2. Put Secure Boot in Setup Mode so you can enroll your own keys.

  3. 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:

/etc/mkinitcpio.conf
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:

/etc/mkinitcpio.d/linux.preset
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:

/etc/kernel/cmdline
rd.luks.options=discard
Warning

§
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
Warning

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:

/etc/systemd/network/10-ethernet.network
[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:

/etc/systemd/journald.conf
[Journal]
SystemMaxUse=100M

Configure system watchdogs:

/etc/systemd/system.conf
[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:

/etc/nftables.conf
#!/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

  1. Reboot to check that everything works.
  2. Install Tailscale, k3s, whatever.
  3. Profit.