Singular file system (LUKS, encrypted)

LUKS (Linux Unified Key Setup) is the standard for Linux hard disk encryption. By providing a standard on-disk-format, it does not only facilitate compatibility among distributions, but also provides secure management of multiple user passwords. LUKS stores all necessary setup information in the partition header, enabling to transport or migrate data seamlessly.

Management of LUKS encrypted devices is done via the cryptsetup utility.

NOTE: Why should you encrypt your data? Encryption ensures that no one but the rightful owner has access to the data. Encryption is therefore not only used to hide sensitive data from prying eyes, it also serves to protect your privacy. Encryption should be considered especially for portable devices such as laptops. In the event of loss or theft, encryption ensures that personal data and secrets (passwords, key files, etc.) do not fall into the wrong hands and are less likely and not as easily be abused.

The simplest, most basic encrypted partitioning scheme in a Linux operating system consists of 3 partitions:

Type File System Description
EFI System Partition vfat Stores boot loaders and bootable OS images in .efi format
Swap LUKS2 Stores swapped memory pages from RAM during high memory pressure
Root File System LUKS2 Stores the Linux OS files (kernel, system libraries, applications, user data)

This guide assumes the following:

Preparing the disk

Determine the disks that are installed on your system. This can easily be done with fdisk:

fdisk -l

It outputs a list of disk devices with one or more entries similar to this:

Disk /dev/nvme0n1: 232.89 GiB, 250059350016 bytes, 488397168 sectors
Disk model: Samsung SSD 840 
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

The line starting the device file with /dev/ is the relevant one. Start partitioning the disk with cfdisk:

WARNING: Make sure you are modifying the correct device, else you will lose data!

cfdisk /dev/nvme0n1

If the disk has no partition table yet, cfdisk will ask you to specify one. The default partition table format for UEFI systems is gpt. Create a layout with at least 3 partitions:

Size FS Type
1G EFI System
(RAM size) Linux Swap
(remaining) Linux root (x86-64)

NOTE: Specifying the correct file system type allows some software to automatically detect and assign appropriate mount points to partitions. See Discoverable Partitions Specification for more details.

You can verfiy that the partitions have been created by running fdisk -l again:

Disk /dev/nvme0n1: 232.89 GiB, 250059350016 bytes, 488397168 sectors
Disk model: Samsung SSD 840 
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

Device             Start       End   Sectors   Size Type
/dev/nvme0n1p1      2048   2099199   2097152     1G EFI System
/dev/nvme0n1p2   2099200  35653631  33554432    16G Linux swap
/dev/nvme0n1p3  35653632 488396799 452743168 215.9G Linux root (x86-64)

This time fdisk will also list the partitions present on the disk.

NOTE: You might notice a pattern with how Linux structures its block devices. Partitions also count as "devices" which you can interact with. Each partition has an incrementing counter attached to its name to specify its order in the partition layout.

Encrypting partitions

Before writing a file system to the disk a LUKS container needs to be created with the cryptsetup utility:

WARNING: Do NOT forget your passphrase! In case of loss you won't be able to access the data inside the container anymore!

NOTE: Assigning labels to partitions creates unique nodes in /dev/disk/by-label/, thereby making them uniquely addressable and easy to discern.

cryptsetup luksFormat --label cryptswap /dev/nvme0n1p2
cryptsetup luksFormat --label cryptroot /dev/nvme0n1p3

Open the newly created LUKS container and supply the passphrase you just set:

NOTE: root is used as an example here. It is the "mapper name" under which the opened LUKS container will be available at for the runtime of the system, in this example: /dev/mapper/root. You may use whatever name you like.

cryptsetup open /dev/disk/by-label/cryptswap swap
cryptsetup open /dev/disk/by-label/cryptroot root

You can also pass additional parameters, e.g. for allowing TRIM on LUKS partitions (often referred to as "discards" in the general context of Linux filesystems):

WARNING: Allowing TRIM on encrypted partitions has security implications. By allowing TRIM, it becomes possible to derive information about the disk's utilization from freed areas. If you need absolute confidentiality, do not enable TRIM!

cryptsetup open /dev/disk/by-label/cryptswap swap --allow-discards
cryptsetup open /dev/disk/by-label/cryptroot root --allow-discards

To save the flags permanently, use the --persistent option. This eliminates the need to specify options manually or in configuration files, since they are stored in the header section of the LUKS container and applied automatically.

You can always update already opened LUKS containers' flags with cryptsetup refresh:

cryptsetup refresh swap --allow-discards --persistent
cryptsetup refresh root --allow-discards --persistent

Formatting and mounting partitions

Create file systems for the ESP and the root file system:

mkfs.fat -F32 -n ESP /dev/nvme0n1p1
mkfs.btrfs --label root /dev/mapper/root
mkswap --label swap /dev/mapper/swap

Create btrfs subvolumes:

mount /dev/mapper/root /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home

Mark the root subvolume @ as the default. This eliminates having to set it later as a kernel rootflags option.

Query the subvolumes, to get the subvolid:

btrfs subvolume list /mnt/

This lists all subvolumes on the partition with their IDs:

ID 256 gen 4839 top level 5 path @
ID 257 gen 4839 top level 5 path @home

The root subvolume @ in this scenario has ID 256:

btrfs subvolume set-default 256 /mnt/
umount -R /mnt

Mount the file systems:

mount /dev/mapper/root -o noatime,compress=zstd,subvol=@ /mnt
mount --mkdir /dev/mapper/root -o noatime,compress=zstd,subvol=@home /mnt/home
mount --mkdir /dev/disk/by-label/ESP /mnt/efi
swapon /dev/mapper/swap

Revision #7
Created 2024-03-05 16:39:31 UTC by Sebin
Updated 2026-07-14 19:16:29 UTC by Sebin