Installing Arch Linux on Btrfs with FDE
2026-01-26
So my SSD kicked the can. What a bummer! And now that I bought a new SSD I have to install Linux all over again but since I didn't write down what I did for my previous install I have to do this entire thing all over again >:( But now I'm going to write everything (well most of it) down for my future self!
I want:
- Standard distro that doesn't take much effort to maintain as I don't always have the time to fix broken configuration
- Full disk encryption (FDE) because I frequently use this laptop in public and it sometimes sits unattended so it's probably a good idea to have at least some encryption
- Separate partitions for root and home for easy backup and rollback if necessary
Bonuses:
- Secure Boot for fun
- Encryption with TPM just because I want to try it :D
Distro choice
I've used a couple distros in the past from Manjaro through Fedora to Arch and NixOS. In fact I'm writing this on NixOS right now and you can see my (terrible) NixOS config in my dotfiles repository!
The problem with NixOS (for me) is that some things don't work or aren't packaged and I don't have the knowledge to fix stuff quickly enough that it doesn't get in the way of school. I'll probably try to summarise my thoughts in another post somewhere down the line.
And since I've spent most of my time before NixOS on Arch based distros, and I feel confident that I can make most things work pretty easily on it so Arch Linux it is!
Other pieces
For booting I'm going to use systemd-boot which will let me select from installed UKIs (Unified Kernel Images). All of this will be signed for verification under Secure Boot.
Since I want full disk encryption I'm going to use LUKS2 as it's the thing that the Arch Linux Wiki recommends and I figure it's going to be the easiest. I'm also going to try and attach the LUKS encryption to TPM along with a PIN to prevent any Evil Maid attacks.
I know that this is very much overkill and way out of scope for my threat model but I just want to try this out for once and it doesn't hurt to be more secure.
Furthermore I want to try using Btrfs with subvolumes which gives me effectively separate partitions for root and home, built in compression, copy on write and easy partition resizing if needed but also lets me (hopefully) easily create snapshots for backup and easy rollback!
Let's start!
In reality most of these steps are following the Arch Linux Installation guide and the instructions for LUKS on partition with TPM2 and Secure Boot. For now this is a very crude set of instructions but I might try to expand on certain decisions in the future.
Pre-installation
This part very much follows the standard guide from Arch Wiki.
- Download and verify the ISO file and flash it to your USB drive of choice
- Boot into the newly created image
- Connect to the internet
- Verify that your time is set correctly (don't worry about timezones for now)
- Properly erase any disks used (see Drive preparation)
Partitioning the disks
I'm going to create a GUID Partition Table with two partitions:
- Boot partition
- Mounted under
/boot - Won't be encrypted but the integrity will be checked by Secure Boot so it's not a problem
- Mounted under
- Main partition
- Encrypted with LUKS2 and decrypted by our UKIs
- Contains a Btrfs filesystem with subvolumes for root, home, swap, etc.
Let's create them then!
fdisk -l- list all of the available drives- I'm going to use
/dev/nvme0n1
- I'm going to use
fdisk /dev/nvme0n1- edit the partition table on the selected drive- Type
mfor help if needed. g- create a new empty GPT partition tablen- add new partition (the first one is going to be the boot partition)- Use the default partition number, first sector
- For last sector use
+1GiB(512M would be enough but I have a lot of space so why not)
t- set partition type- Use ID
1oruefialias to set the partition type toEFI System
- Use ID
n- add new partition (the LUKS partition this time)- Leave everything at their default values
t- set partition type- Select partition
2 - Use ID
20orlinuxalias to set the partition type toLinux filesystem
- Select partition
- (optional)
x- enter the extra functionalit menun- set a new partition name (these labels will be saved in the Partition Table)1- select first partitionESP- name it ESP
n- set a new partition name2- select first partitionLUKS- name it LUKS
r- return to the main menu
w- write the changes
- Type
Formatting partitions
LUKS partition
cryptsetup luksFormat /dev/nvme0n1p2- Creates a LUKS volume
- Pick a safe passphrase!
cryptsetup config --label LUKS /dev/nvme0n1p2- Sets the label in the LUKS2 header to
LUKS
- Sets the label in the LUKS2 header to
cryptsetup open /dev/nvme0n1p2 cryptroot- Opens the LUKS volume under
/dev/mapper/cryptroot
- Opens the LUKS volume under
mkfs.btrfs -L torus /dev/mapper/cryptroot- Formats the LUKS container with Btrfs
- Gives it the lable
torus
mount /dev/mapper/cryptroot /mnt
Btrfs subvolume creation
To be honest I'm not sure this is the best layout and with some research and thinking I could probably come up with something better.
The idea behind this is that @ (the root subvolume)
and @home are on separate subvolumes and are thus
able to be snapshotted separately from each other.
This allows me to roll back some changes to root
while keeping everything in home which is usually
what I would want.
Then there are volumes for logs, caches and swap
as logs is something I would want to retain across
rollbacks if possible and caches are something
that would just take up space in caches.
I'll probably try to do something simmiliar
for the caches inside home such as ~/.cache.
-
cd /mnt- enter the/mntdirectory to create btrfs subvolumesbtrfs subvolume create @- root subvolumebtrfs subvolume create @home- home subvolumebtrfs subvolume create @log-/var/logsubvolume (to retain logs even after a rollback)chattr +C @log
btrfs subvolume create @cache-/var/cachesubvolume (to not clutter root snapshots)chattr +C @cache- disable CoW (Copy on Write)
btrfs subvolume create @tmp-/var/tmpsubvolumechattr +C @tmp
btrfs subvolume create @swap- swap subvolumechattr +C @swap
- Set
@as the default subvolumebtrfs subvolume list .- grab the ID of the@pathbtrfs subvolume set-default <id> .- replace<id>with the one obtained above
-
cd ../ -
umount /mnt -
TODO: Decide on using TRIM as it somewhat compromises LUKS2 (see this and this)
- Since I'm already writing about using btrfs and my specific threat model isn't very concerned with someone finding out how much data I have saved or what filesystem is in us.
- I think the SSD health benefit is worth it for me
- TRIM can be enabled under btfs with the
discard=asyncmount option- This would also require to use the
discardoption when mount LUKS (see dm-crypt wiki)
- This would also require to use the
Mounting Btrfs subvolumes
We mount all of the necessary partitions
to where we want them inside /mnt.
This will then allow us to run genfstab
and easily obtain the correct /etc/fstab
entries without having to create them manually.
mount -o ssd,compress=zstd:1,subvol=@ --mkdir /dev/mapper/cryptroot /mntmount -o ssd,compress=zstd:1,subvol=@home --mkdir /dev/mapper/cryptroot /mnt/homemount -o ssd,compress=zstd:1,subvol=@log --mkdir /dev/mapper/cryptroot /mnt/var/logmount -o ssd,nodatacow,noatime,subvol=@cache --mkdir /dev/mapper/cryptroot /mnt/var/cachemount -o ssd,nodatacow,noatime,subvol=@tmp --mkdir /dev/mapper/cryptroot /mnt/var/tmpmount -o ssd,nodatacow,noatime,subvol=@swap --mkdir /dev/mapper/cryptroot /mnt/swapmkswap --file -L SWAPFILE --size 64G /mnt/swap/swapfile- This is for a 64GiB swap file! (Yes, it's overkill I know)
- Adjust this for your own needs
swapon /mnt/swap/swapfile- Hibernation support:
btrfs inspect-internal map-swapfile -r /mnt/swap/swapfile- Use
resume=UUID=XXXXXXXXXXXXXXXXXXXXXXXXXX resume_offset=XXXXas kernel parameters
Boot partition
Next time I could try to structure the boot partition such that configuration is inside root and only UKIs and the bootloader are outside in the boot partition.
If this setup is used with Secure Boot it's perhaps not so much of a problem.
mkfs.fat -F 32 -n EPS /dev/nvme0n1p1- Formats our boot partition as FAT32
- Sets the label to
ESP
mount --mkdir /dev/nvme0n1p1 /mnt/boot
Installation
pacstrap -K /mnt base linux linux-firmware <microcode> btrfs-progs networkmanager zsh git openssh curl wget neovim man-db man-pages texinfo efibootmgr sbctl- Change
<microcode>forintel-ucodeoramd-ucode
- Change
Configure the system
-
genfstab -U /mnt >> /mnt/etc/fstab- Add
nodatacowoption to/var/cache,/var/tmpand/swap - Add
noatimeoption to/var/log,/var/cache,/var/tmpand/swap
- Add
-
arch-chroot /mntln -sf /usr/share/zoneinfo/<area>/<location> /etc/localtime- Use
<TAB>for autocompletion and pick your desired location
- Use
hwclock --systohcnvim /etc/locale.gen- Uncomment locales you want to generatelocal-genecho "LANG=en_US.UTF-8" > /etc/locale.conf
echo "KEYMAP=us" > /etc/vconsole.confecho "<hostname>" > /etc/hostanme
Configure mkinitcpio
I'm not sure if mkinitcpio is the easiest and perhaps
using dracut would be better but this is what
the Arch Wiki recommends so I'm going to try it first.
At the time of writing I did:
- Modify
/etc/mkinitcpio.conf- Add
sd-encryptafter theblockoption in theHOOKSarrayHOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)
- Add
- Modify
/etc/mkinitcpio.d/linux.presetlike this:# mkinitcpio preset file for the 'linux' package #ALL_config="/etc/mkinitcpio.conf" ALL_kver="/boot/vmlinuz-linux" PRESETS=('default' 'fallback') #default_config="/etc/mkinitcpio.conf" #default_image="/boot/initramfs-linux.img" default_uki="/boot/EFI/Linux/arch-linux.efi" default_options="--splash=/usr/share/systemd/bootctl/splash-arch.bmp" #fallback_config="/etc/mkinitcpio.conf" #fallback_image="/boot/initramfs-linux-fallback.img" fallback_uki="/boot/EFI/Linux/arch-linux-fallback.efi" fallback_options="-S autodetect"
Setting the kernel commandline arguments is critical and we can't use the default options as LUKS + BTRFS subvolumes are a bit non standard.
- Set
/etc/kernel/cmdlineLUKSUUID="$(blkid | grep nvme0n1p2 | awk -F ' ' '{ print $2 }' | awk -F '"' '{ print $2 }')"ROOTUUID="$(lsblk -f | grep cryptroot | awk -F' ' '{ print $4 }')"echo "rd.luks.name=$LUKSUUID=cryptroot root=/dev/mapper/root rootflags=subvol=@ rw bgrt_disable" > /etc/kernel/cmdline
rd.luks.name tells the kernel that we're using a LUKS encrypted partition.
The kernel decrypts it and mounts it to /dev/mapper/cryptroot.
We then tell the kernel where to find the root and with
what flags to mount it
(this is only temporary so we probably don't need to use all the
flags that are specified inside /etc/fstab)
I recommend not setting the quiet parameter at least
for the first couple of boots for debugging purposes.
Configure the boot loader
Using a bootloader will allow us to choose from multiple UKIs in case one fails for some reason.
I should probably create a better fallback UKI configuration than the one here but... that's a problem for future me!
bootctl install- Automatic updates and signing
- I went for the pacman hook
- UKIs should be automatically sourced so no entries need to be added
- Automatic updates and signing
mkinitcpio -P- generate UKIs otherwise we won't have anything to boot into!passwd- don't forget this!useradd -m -G wheel -s /usr/bin/zsh <user>passwd <user>EDITOR=nvim visudo- uncomment the first%wheeloptionexit- exit the chroot environmentreboot
To be continued...
Aaaand it doesn't boot with TPM2 enabled with an error that I traced to this issue. I don't want to deal with this right now so all I have is FDE without Secure Boot or TPM at the moment but it's something I will revesit later and update this article when I do so!
Setting up Secure Boot
sbctl create-keyssbctl enroll-keys- Errors out presumably because of disabled TPM
Setting up the user environment
I pretty much just installed my dotfiles which needed only minor tweaks to get working!
Future work
I definitely want to get Secure Boot and TPM working and I also want to create better fallback and some automated backup system.
In the far future I want to create a configuration
language that would basically be a better
home-manager and then I would probably try
to revisit this project with a blank sheet and
try my hand at automating most of this.
Anyways if anyone is reading this I hope it helped at least a little bit. If you find any issues feel free to send me an email or some other type of message!
And... I use Arch btw. :)