Arch Linux Full-Disk Encryption Installation Guide

This guide provides instructions for an Arch Linux installation featuring full-disk encryption via LVM on LUKS and an encrypted boot partition (GRUB) for UEFI systems.

For advanced security, the system can be further hardened against Evil Maid attacks using UEFI Secure Boot with custom enrolled keys and a self-signed kernel/bootloader.

Preface

Most of this is from the Arch Wiki and other resources linked thereof.

Based on huntrar's gist installation guide.

Note: The system was installed on an NVMe SSD, substitute /dev/nvme0nX with /dev/sdX or your device as needed.

Modern Arch Linux installations often use the archinstall tool. This guide takes the manual route instead: useful for understanding what's happening under the hood, or on systems where automated tools aren't available.

Table of contents

Create USB stick

wget http://archlinux.mirrors.ovh.net/archlinux/iso/latest/archlinux-x86_64.iso
wget http://archlinux.mirrors.ovh.net/archlinux/iso/latest/archlinux-x86_64.iso.sig

gpg --keyserver pgp.mit.edu --keyserver-options auto-key-retrieve --verify archlinux-x86_64.iso.sig

sudo dd bs=4M if=archlinux-*.iso of=/dev/sda status=progress oflag=sync

where /dev/sda is your usb key

From live

NumberStart (sector)End (sector)SizeCodeName
1204840951024.0 KiBEF02BIOS boot partition
240961130495550.0 MiBEF00EFI System
31130496976773134465.2 GiB8309Linux LUKS

Partitions

Create

gdisk /dev/nvme0n1
o
n
[Enter]
0
+1M
ef02
n
[Enter]
[Enter]
+550M
ef00
n
[Enter]
[Enter]
[Enter]
8309
w

LUKS

cryptsetup luksFormat --type luks1 --use-random -S 1 -s 512 -h sha512 -i 5000 /dev/nvme0n1p3
cryptsetup luksOpen /dev/nvme0n1p3 cryptlvm

LVM

RAM_SIZE=$(($(getconf _PHYS_PAGES) * $(getconf PAGE_SIZE) / (1024 * 1024)))

pvcreate /dev/mapper/cryptlvm
vgcreate archlvm /dev/mapper/cryptlvm

lvcreate -L 32G archlvm -n slash
lvcreate -L 30G archlvm -n opt
lvcreate -L 10G archlvm -n var_lib_docker
lvcreate -L "${RAM_SIZE}M" archlvm -n swap
lvcreate -l 100%FREE archlvm -n home

Format

mkfs.fat -F32 /dev/nvme0n1p2                     -n EFI
mkfs.ext4     /dev/mapper/archlvm-slash          -L slash
mkfs.ext4     /dev/mapper/archlvm-home           -L home
mkfs.ext4     /dev/mapper/archlvm-opt            -L opt
mkfs.ext4     /dev/mapper/archlvm-var_lib_docker -L var_lib_docker
mkswap        /dev/mapper/archlvm-swap           -L swap
swapon        /dev/mapper/archlvm-swap

Mount

mount /dev/mapper/archlvm-slash /mnt
mkdir /mnt/efi /mnt/home /mnt/var/lib/docker /mnt/opt -p
mount /dev/nvme0n1p2                     /mnt/efi
mount /dev/mapper/archlvm-home           /mnt/home
mount /dev/mapper/archlvm-var_lib_docker /mnt/var/lib/docker
mount /dev/mapper/archlvm-opt            /mnt/opt
chmod 700 /boot

System

Install base

# Available kernel options:
# KERNEL='linux'     # Vanilla Linux kernel and modules, with a few patches applied.
# KERNEL='linux-lts' # Long-term support (LTS) Linux kernel and modules.
# KERNEL='linux-zen' # Result of a collaborative effort of kernel hackers to provide the best Linux kernel possible for everyday systems.
# KERNEL='linux-hardened' # Security-focused kernel with additional hardening features

# Modern archinstall typically recommends 'linux' for most users
KERNEL='linux'

# Available microcode options:
# UCODE='intel-ucode' # for Intel processors.
# UCODE='amd-ucode'   # for AMD processors.

UCODE='intel-ucode' # for Intel processors, adjust as needed

pacstrap /mnt \
  base \
  base-devel \
  ${KERNEL} \
  ${KERNEL}-headers \
  ${UCODE} \
  wireless-regdb \
  efibootmgr \
  git \
  grub \
  irqbalance \
  linux-firmware \
  lvm2 \
  fastfetch \
  network-manager-applet \
  networkmanager \
  openssh \
  os-prober \
  python \
  resolvconf \
  rsync \
  sudo \
  terminus-font \
  vim \
  wpa_supplicant \
  zsh

Configure resolv.conf

echo '[main]
rc-manager=resolvconf' > /mnt/etc/NetworkManager/conf.d/rc-manager.conf

Configure wifi

echo 'WIRELESS_REGDOM="FR"' > /mnt/etc/conf.d/wireless-regdom
echo "options iwlwifi power_save=1"     > /mnt/etc/modprobe.d/iwlwifi.conf
echo "options iwlwifi uapsd_disable=0" >> /mnt/etc/modprobe.d/iwlwifi.conf
if grep -wq '^iwlmvm' /proc/modules; then
  echo "options iwlmvm power_scheme=3" >> /mnt/etc/modprobe.d/iwlwifi.conf
elif grep -wq '^iwldvm' /proc/modules; then
  echo "options iwldvm force_cam=0"    >> /mnt/etc/modprobe.d/iwlwifi.conf
fi

Sound

if grep -wq '^snd_hda_intel' /proc/modules; then
echo "options snd_hda_intel power_save=1" > /mnt/etc/modprobe.d/audio_powersave.conf
elif grep -wq '^snd_ac97_codec' /proc/modules; then
echo "options snd_ac97_codec power_save=1" > /mnt/etc/modprobe.d/audio_powersave.conf
fi

Create fstab

genfstab -U /mnt                                           >> /mnt/etc/fstab
echo 'tmpfs     /tmp tmpfs defaults,noatime,mode=1777 0 0' >> /mnt/etc/fstab
sed -i 's/relatime/noatime/g' /mnt/etc/fstab

From chroot

arch-chroot /mnt

At this point you should have the following partitions and logical volumes: lsblk

NAMEMAJ:MINRMSIZEROTYPEMOUNTPOINT
nvme0n1259:00953.9G0disk
├─nvme0n1p1259:101M0part
├─nvme0n1p2259:20550M0part/efi
├─nvme0n1p3259:30953.3G0part
..└─cryptlvm254:00953.3G0crypt
....├─archlvm-swap254:1031.1G0lvm[SWAP]
....├─archlvm-slash254:2032G0lvm/
....└─archlvm-home254:30100G0lvm/home
....└─archlvm-opt254:4030G0lvm/opt
....└─archlvm-var_lib_docker254:5010G0lvm/var/lib/docker

Build flags

Use all cores for builds:

sed -i 's/^CXXFLAGS.*/CXXFLAGS="-march=native -mtune=native -O2 -pipe -fstack-protector-strong --param=ssp-buffer-size=4 -fno-plt"/' /etc/makepkg.conf && \
sed -i 's/^#RUSTFLAGS.*/RUSTFLAGS="-C opt-level=2 -C target-cpu=native"/' /etc/makepkg.conf && \
sed -i 's/^#BUILDDIR.*/BUILDDIR=\/tmp\/makepkg/' /etc/makepkg.conf && \
sed -i 's/^#MAKEFLAGS.*/MAKEFLAGS="-j$(getconf _NPROCESSORS_ONLN) --quiet"/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSGZ.*/COMPRESSGZ=(pigz -c -f -n)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSBZ2.*/COMPRESSBZ2=(pbzip2 -c -f)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSXZ.*/COMPRESSXZ=(xz -T "$(getconf _NPROCESSORS_ONLN)" -c -z --best -)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSZST.*/COMPRESSZST=(zstd -c -z -q --ultra -T0 -22 -)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSLZ.*/COMPRESSLZ=(lzip -c -f)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSLRZ.*/COMPRESSLRZ=(lrzip -9 -q)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSLZO.*/COMPRESSLZO=(lzop -q --best)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSZ.*/COMPRESSZ=(compress -c -f)/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSLZ4.*/COMPRESSLZ4=(lz4 -q --best)/' /etc/makepkg.conf

Time zone

timedatectl set-timezone "$(curl -s --fail https://ipapi.co/timezone)"
timedatectl set-ntp true
timedatectl
hwclock --systohc

Locales

sed -i 's/^#fr_FR/fr_FR/g' /etc/locale.gen
sed -i 's/^#en_US/en_US/g' /etc/locale.gen
locale-gen
echo 'LANG=en_US.UTF-8'  > /etc/locale.conf

Keymap

echo 'KEYMAP=us-acentos' > /etc/vconsole.conf
echo 'FONT=ter-116n'    >> /etc/vconsole.conf

mkdir -p /etc/X11/xorg.conf.d
cat <<EOF>/etc/X11/xorg.conf.d/00-keyboard.conf
# Read and parsed by systemd-located. It's probably wise not to edit this file
# manually too freely.
Section "InputClass"
        Identifier "system-keyboard"
        MatchIsKeyboard "on"
        Option "XkbLayout" "us"
        Option "XkbVariant" "intl"
EndSection
EOF

Hostname

myhostname='MyArch'
echo "${myhostname}" > /etc/hostname
cat <<EOF>> /etc/hosts
127.0.0.1  localhost
127.0.1.1  ${myhostname} ${myhostname}.localdomain
::1        localhost ip6-localhost ip6-loopback
ff02::1    ip6-allnodes
ff02::2    ip6-allrouters
EOF

Boot

GRUB

UUID=$(blkid /dev/nvme0n1p3 -s UUID -o value)

sed -i "/^GRUB_CMDLINE_LINUX=/cGRUB_CMDLINE_LINUX=\"cryptdevice=UUID=${UUID}:cryptlvm root=/dev/mapper/archlvm-slash cryptkey=rootfs:/root/.cryptlvm/archluks.bin\""  /etc/default/grub

sed -i "/GRUB_ENABLE_CRYPTODISK=/cGRUB_ENABLE_CRYPTODISK=y" /etc/default/grub

grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=ArchLinux
grub-mkconfig -o /boot/grub/grub.cfg

initramfs

mkdir /root/.cryptlvm && chmod 700 /root/.cryptlvm
head -c 64 /dev/urandom > /root/.cryptlvm/archluks.bin && chmod 600 /root/.cryptlvm/archluks.bin
# Note: -i 1 sets the PBKDF iterations time to 1ms for faster boot. Increase for stronger security.
cryptsetup -v luksAddKey -i 1 /dev/nvme0n1p3 /root/.cryptlvm/archluks.bin

# For Intel graphics; for AMD use: MODULES=(amdgpu), for Nvidia use: MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)
sed -i '/^MODULES/c\MODULES=(intel_agp i915)' /etc/mkinitcpio.conf
sed -i '/^FILES/c\FILES=(/root/.cryptlvm/archluks.bin)' /etc/mkinitcpio.conf
sed -i '/^HOOKS/c\HOOKS=(base udev autodetect modconf block keyboard keymap consolefont encrypt lvm2 filesystems fsck)' /etc/mkinitcpio.conf
mkinitcpio -P

Services

systemctl enable NetworkManager
systemctl enable systemd-timesyncd.service

User

MYUSER='MyUser'
useradd -m -s /bin/zsh -G network,users,storage,lp,input,audio,wheel ${MYUSER}
echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/wheel
passwd ${MYUSER}

Package manager

# Enable color, checksum, verbose output, and the official multilib repository.
sed -i 's/#Color/Color/' /etc/pacman.conf
sed -i 's/#CheckSpace/CheckSpace/' /etc/pacman.conf
sed -i 's/#UseSyslog/UseSyslog/' /etc/pacman.conf
sed -i '/^#\[multilib\]/,/^#Include = \/etc\/pacman.d\/mirrorlist/ s/^#//' /etc/pacman.conf
pacman -Syu

multilib must be enabled before running the dotfiles installer because the base and Steam package groups contain lib32-* packages.

Dotfiles

# Run setup commands in the regular user's context.
sudo -u "${MYUSER}" -i bash -c '
  git clone https://gitlab.com/pad92/dotfiles.git ~/.dotfiles
  mkdir -p ~/.config
  ~/.dotfiles/install
'

AUR

# Modern archinstall typically handles AUR package management automatically
# For manual installation, 'yay' is a popular choice for AUR packages

# Install yay (AUR helper) as the regular user created above.
sudo pacman -Syu --needed --noconfirm git base-devel sudo
sudo -u "${MYUSER}" -i bash -c '
  tmpdir=$(mktemp -d)
  trap '\''rm -rf "$tmpdir"'\'' EXIT
  git clone https://aur.archlinux.org/yay-bin.git "$tmpdir/yay-bin"
  cd "$tmpdir/yay-bin"
  makepkg -si --noconfirm
'

# Alternative AUR helpers:
# - paru (more lightweight)
# - pikaur (Python-based)
# - pamac (GUI-based, if using a desktop environment)

WM and apps

Packages and software are installed via the dotfiles setup script (~/.dotfiles/install).

To install or re-install packages manually:

yay -S --needed $(cat ~/.dotfiles/dist/arch/packages/*.txt)

Or re-run the installer:

~/.dotfiles/install
exit
sync
exit
umount -R /mnt
reboot

Optional

Auto CPUfreq

sudo systemctl enable --now auto-cpufreq

SSD Trim

sudo pacman -S util-linux
sudo systemctl enable fstrim.timer

USBGuard

yay -S usbguard usbguard-applet-qt
sudo usbguard generate-policy | sudo tee /etc/usbguard/rules.conf
sudo systemctl start usbguard.service
sudo systemctl enable usbguard.service

Docker

yay -S docker docker-compose
usermod -a -G docker MyUser

Nvidia

sed -i '/^MODULES/c\MODULES=(nvidia)' /etc/mkinitcpio.conf
yay -S nvidia-dkms nvidia-utils
sudo mkinitcpio -P

Nvidia Prime

yay -S nvidia-dkms nvidia-utils nvidia-prime

Spotify

Remove notification

echo 'ui.track_notifications_enabled=false' > ~/.config/spotify/Users/*-user/prefs

GNOME Keyring PAM Setup

Add pam_gnome_keyring.so to /etc/pam.d/login to automatically unlock GNOME Keyring on TTY login:

#%PAM-1.0

auth       requisite    pam_nologin.so
auth       include      system-local-login
-auth      optional     pam_gnome_keyring.so
account    include      system-local-login
password   include      system-local-login
-password  optional     pam_gnome_keyring.so    use_authtok
session    include      system-local-login
-session   optional     pam_gnome_keyring.so    auto_start

Systemd Startup (UWSM)

For complete documentation, see Hyprland Wiki.

UWSM (Universal Wayland Session Manager) wraps the compositor in Systemd units for robust environment, application, and session management.

Installation

sudo pacman -S uwsm libnewt

Launch from TTY

This dotfiles setup installs the following logic in ~/.zprofile, so it runs only for a login shell attached to a terminal:

Option A: Interactive selection menu at login

if [[ -o login && -t 0 ]] && command -v uwsm >/dev/null 2>&1; then
    if uwsm check may-start && uwsm select; then
        exec uwsm start default
    fi
fi

Option B: Direct launch

if [[ -o login && -t 0 ]] && command -v uwsm >/dev/null 2>&1; then
    if uwsm check may-start; then
        exec uwsm start hyprland.desktop
    fi
fi

Note: For display managers, select Hyprland (uwsm-managed).

Application Launching

Launch graphical applications as Systemd scopes:

uwsm app -- alacritty

Autostart

  • XDG Autostart: Handled automatically by the Systemd session target.
  • Native User Services:
    systemctl --user enable <service>
    # If the service lacks an [Install] section:
    systemctl --user add-wants graphical-session.target <service>
    

Real-time Audio & Latency (Gaming/Work/VoIP)

To allow PipeWire and WirePlumber to run with real-time scheduling (preventing audio stuttering, clicks, and dropouts under heavy CPU load, such as in games or during compilation on a work machine):

  1. Install the realtime-privileges package, which configures the necessary PAM limits and creates the realtime group:
sudo pacman -S realtime-privileges
  1. Add your user to the realtime group:
sudo usermod -aG realtime MyUser

Note: You must log out and log back in (or reboot) for the group membership to take effect. If you don't do this, PipeWire logs will show mod.rt: could not set nice-level to -11: Permission denied.

Modern Security Practices

archinstall bakes some of this in by default; a manual install doesn't, so it's worth adding separately:

  • firewalld for network filtering
  • fail2ban against SSH brute-force attempts
  • automatic security updates via pacman-contrib and reflector
  • SSH hardened to key-only auth, root login disabled
  • systemd-boot instead of GRUB, if you don't need os-prober
  • an extra encryption layer on the initramfs
  • a backup plan for the LUKS keyfiles, not just the data they protect