Install Debian 12 with encrypted Root-on-ZFS
Debian 12 aka "Bookworm" is the latest stable release of the popular Linux operating system. I use Debian's live install image to create a minimal, console-only base configuration using the OpenZFS filesystem with native encryption.
This is how I do it ...
1. Let's go!
HOWTO configuration:
- Debian is the sole OS on a single SATA SSD disk
- Target device is
x86_64
architecture - System uses UEFI to boot and ZFSBootMenu as bootloader
- ZFS native encryption is enabled
- Disk contains a GPT partition table with 3 partitions:
- Partition:
/dev/sda1
-- Size:500MB
-- Type:EFI System Partition
-- Mount:/boot/efi
- Partition:
/dev/sda2
-- Size:Amount of RAM + 1GB
-- Type:Swap (encrypted)
-- Mount:swap
- Partition:
/dev/sda3
-- Size:Remaining disk space
-- Type:ZFS Pool (encrypted)
-- Mount:/
- Partition:
1.1 Prepare installation media
Prepare a USB flash drive as an installer using either Ventoy or dd.
Download debian-live-12.4.0-amd64-standard.iso
and SHA256SUMS
either by torrent or direct download from Live Install Images.
Verify image integrity ...
$ sha256sum -c --ignore-missing SHA256SUMS
debian-live-12.4.0-amd64-standard.iso: OK
Prepare a USB storage device as an installer using one of these two methods:
Method #1: Ventoy
I now use Ventoy to setup a USB device to be a multiboot installer. Simply copy an iso to the device, reboot, and the auto-generated menu lists all the disk images available to boot. Read more
Method #2: dd
Write the installer to an unmounted USB storage device using the dd
command as root.
BE VERY CAREFUL TO NOTE THE PROPER DEVICE. ALL DATA ON THE DEVICE WILL BE OVERWRITTEN.
Example: On a Linux system, if a USB stick appears as sdx1
, then write the installer to sdx
(no partition number) ...
# dd if=/path/to/debian-live-12.4.0-amd64-standard.iso of=/dev/sdX bs=1M status=progress oflag=sync
1.2 Boot
debian login: user (automatic login)
Switch to a root shell ...
$ sudo -i
Confirm EFI support ..
# dmesg | grep -i efivars
[ 0.301784] Registered efivars operations
2. Configure live environment
2.1 Network
Wired (ethernet) interfaces are configured for auto-detection and to use DHCP to retrieve an IP address.
Display all detected network interfaces along with their IP and MAC addresses ...
$ ip addr
Debian's network interfaces are configured in /etc/network/interfaces
and controlled by the ifup
and ifdown
commands.
Example entry for wired ...
allow-hotplug enp0s31f6
iface enp0s31f6 inet dhcp
Example entry for wireless ...
allow-hotplug wlp61s0
iface wlp61s0 inet dhcp
wpa-ssid <wifi_access_point_name>
wpa-psk <wifi_passphrase>
2.2 Optional: Set keyboard
Default console keymap is US QWERTY
.
For a different keymap, install ...
# apt install console-data
Available keymaps are listed in /usr/share/keymaps/
.
Select a different keymap by running ...
# dpkg-reconfigure keyboard-configuration
Reload ...
# setupcon
2.3 Optional: Set console font
Discover available fonts in /usr/share/consolefonts
.
Default font in the installer might prove to small on high resolution displays. Set and display a larger font (example: Lat15-Terminus11x22
) for the current session ...
# setfont Lat15-Terminus11x22
# showconsolefont
2.4 Optional: Continue install from another computer via SSH
This makes the install easier by cut-and-pasting these commands into a terminal.
Install ...
# apt install openssh-server
SSH server is auto-launched after install.
Assign password to user
...
# passwd user
SSH into the target device ...
$ ssh user@<ip_address>
Switch to root ...
$ sudo -i
2.5 Configure APT
# cat <<EOF > /etc/apt/sources.list
deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb http://deb.debian.org/debian-security/ bookworm-security main contrib non-free non-free-firmware
EOF
Update package database ...
# apt update
2.6 Install helpers
# apt install debootstrap gdisk dkms linux-headers-$(uname -r) parted
# apt install zfsutils-linux zfs-dkms zfs-zed
Test ...
# modprobe zfs
# zfs version
zfs-2.1.11-1
zfs-kmod-2.1.11-1
2.7 Generate /etc/hostid
# zgenhostid -f 0x00bab10c
2.8 Set environment variables
File /etc/os-release
defines variables that describe the current operating system. Use the $ID
variable to set the short name of the distribution in later commands ...
# source /etc/os-release
# export ID
Identify the internal storage device where Debian will be installed by running lsblk
.
Set disk variables for either a SATA or NVME disk:
SATA DISK
# export DISK="/dev/sda"
# export BOOT_PART="1"
# export SWAP_PART="2"
# export POOL_PART="3"
# export BOOT_DISK="${DISK}${BOOT_PART}"
# export SWAP_DISK="${DISK}${SWAP_PART}"
# export POOL_DISK="${DISK}${POOL_PART}"
NVME DISK
# export DISK="/dev/nvme0n1"
# export BOOT_PART="1"
# export SWAP_PART="2"
# export POOL_PART="3"
# export BOOT_DISK="${DISK}p${BOOT_PART}"
# export SWAP_DISK="${DISK}p${SWAP_PART}"
# export POOL_DISK="${DISK}p${POOL_PART}"
3. Partition disk
3.1 Wipe old partitions
# zpool labelclear -f "$POOL_DISK"
# wipefs -af "$DISK"
# sgdisk --zap-all "$DISK"
# partprobe "$DISK"
3.2 Create new partitions
List partition type codes ...
# sgdisk --list-types
Create EFI partition ...
# sgdisk -n "${BOOT_PART}:1m:+500m" -t "${BOOT_PART}:ef00" -c 0:esp "$DISK"
Create swap partition, sized as amount of RAM (example: 8GB) + 1GB ...
# sgdisk -n "${SWAP_PART}:0:+9g" -t "${SWAP_PART}:8200" -c 0:swap "$DISK"
Create zpool partition (remaining disk space)...
# sgdisk -n "${POOL_PART}:0:-10m" -t "${POOL_PART}:bf00" -c 0:pool "$DISK"
Show ...
# partprobe "$DISK"
# sgdisk -p "$DISK"
4. ZFS pool creation
When adding disks or partitions to ZFS pools, its important use their symbolic links created in /dev/disk/by-id
or (on UEFI systems) /dev/disk/by-partuuid
. This will ensure that ZFS identifies the correct device even if disk naming should change at some point. Using traditional device nodes like /dev/sda3
may cause import failures.
So I create another variable to hold the PARTUUID
of $POOL_DISK
...
# export POOL_ID=/dev/disk/by-partuuid/$( blkid | grep "${POOL_DISK}" | cut -d ' ' -f 3 | cut -d '"' -f 2 )
4.1 Store pool passphrase in key file
# echo 'SomeKeyphrase' > /etc/zfs/zroot.key
# chmod 000 /etc/zfs/zroot.key
4.2 Create encrypted zpool
Create a ZFS pool on the partition created for it using zpool
...
# zpool create -f -o ashift=12 \
-O compression=lz4 \
-O acltype=posixacl \
-O xattr=sa \
-O relatime=on \
-o autotrim=on \
-O encryption=aes-256-gcm \
-O keylocation=file:///etc/zfs/zroot.key \
-O keyformat=passphrase \
-m none zroot "${POOL_ID}"
4.3 Create ZFS file systems
# zfs create -o mountpoint=none zroot/ROOT
# zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT/${ID}
# zfs create -o mountpoint=/home zroot/home
Set the preferred boot file system ...
# zpool set bootfs=zroot/ROOT/${ID} zroot
4.4 Export and re-import the pool
# zpool export zroot
# zpool import -N -R /mnt zroot
4.5 Mount root and home
# zfs load-key -L prompt zroot
# zfs mount zroot/ROOT/${ID}
# zfs mount zroot/home
Verify mountpoints ...
# mount -t zfs
zroot/ROOT/debian on /mnt type zfs (rw,relatime,xattr,posixacl)
zroot/home on /mnt/home type zfs (rw,relatime,xattr,posixacl)
4.6 Update device symlinks
# udevadm trigger
5. Install and configure Debian
5.1 Install Debian
# debootstrap bookworm /mnt
5.2 Copy files into the new install
# cp /etc/hostid /mnt/etc/
# cp /etc/resolv.conf /mnt/etc/
# mkdir /mnt/etc/zfs
# cp /etc/zfs/zroot.key /mnt/etc/zfs/
5.3 Chroot into the new OS
# mount -t proc proc /mnt/proc
# mount -t sysfs sys /mnt/sys
# mount -B /dev /mnt/dev
# mount -t devpts pts /mnt/dev/pts
# chroot /mnt /bin/bash
5.4 Set root password
# passwd
5.5 Set hostname
# echo 'YOURHOSTNAME' > /etc/hostname
# echo -e '127.0.1.1\tYOURHOSTNAME' >> /etc/hosts
5.6 Configure APT package manager
Debian uses separate archives to distinguish between software packages based on their licenses:
- Main is enabled by default and includes everything that satisfies the conditions of the Debian Free Software Guidelines.
- Contrib packages are open-source themselves but rely on software in non-free to work.
- Non-free contains packages that do not meet all the conditions of the DFSG but can be freely distributed.
- Non-free-firmware (introduced in Debian 12) contains non-free firmware binaries packaged for devices that would be completely non-functional without them (example: many wireless cards).
- Backports contains packages drawn from the testing (and sometimes unstable) archive and modified to work in the current stable release.
# cat <<EOF > /etc/apt/sources.list
deb http://deb.debian.org/debian/ bookworm main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian/ bookworm main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
deb-src http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
deb http://deb.debian.org/debian/ bookworm-updates main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian/ bookworm-updates main contrib non-free non-free-firmware
deb http://deb.debian.org/debian/ bookworm-backports main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian/ bookworm-backports main contrib non-free non-free-firmware
EOF
Any time sources.list is modified its necessary to update the package database ...
# apt update
5.7 Install additional base packages
# apt install console-setup cryptsetup curl dosfstools efibootmgr keyboard-configuration locales
5.8 Set timezone
# dpkg-reconfigure tzdata
5.9 Configure locales
# dpkg-reconfigure locales
NOTE: Always enable the en_US.UTF-8
locale because some programs require it.
5.10 Set console font
# dpkg-reconfigure console-setup
# setupcon
5.11 Set keyboard
# dpkg-reconfigure keyboard-configuration
5.12 Create user account
# adduser NEW_USERNAME
5.13 Network
Display all detected network interfaces along with their IP and MAC addresses ...
$ ip addr
Debian's network interfaces are configured in /etc/network/interfaces
and controlled by the ifup
and ifdown
commands.
Example entry for wired ...
allow-hotplug enp0s25
iface enp0s31f6 inet dhcp
Example entry for wireless ...
allow-hotplug wlp3s0
iface wlp61s0 inet dhcp
wpa-ssid <wifi_access_point_name>
wpa-psk <wifi_passphrase>
NOTE: If relying solely on a wireless interface for network access, be sure to install the necessary packages for the wireless device. See the Debian Wiki for details.
5.14 Optional: Enable remote access using OpenSSH
# apt install openssh-server
SSH is enabled by default to launch at boot.
6. Configure EFI and swap
6.1 Create vfat filesystem
# mkfs.vfat -F32 "$BOOT_DISK"
6.2 Create encrypted swap
Modify /etc/crypttab
...
# echo "swap /dev/disk/by-partlabel/swap /dev/urandom swap,offset=2048,cipher=aes-xts-plain64,size=512" >> /etc/crypttab
This will map /dev/disk/by-partlabel/swap
to /dev/mapper/swap
as a swap partition that can be added in /etc/fstab
like a normal swap.
6.3 Create fstab entries
# cat << EOF > /etc/fstab
$( blkid | grep "$BOOT_DISK" | cut -d ' ' -f 2 ) /boot/efi vfat defaults 0 0
/dev/mapper/swap none swap defaults 0 0
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
EOF
6.4 Mount efi
# mkdir -p /boot/efi
# mount /boot/efi
7. ZFS Configuration
7.1 Install required packages
# apt install linux-headers-amd64 linux-image-amd64 zfs-initramfs dosfstools
# echo "REMAKE_INITRD=yes" > /etc/dkms/zfs.conf
7.2 Enable systemd ZFS services
# systemctl enable zfs.target
# systemctl enable zfs-import-cache
# systemctl enable zfs-mount
# systemctl enable zfs-import.target
7.3 Configure initramfs
# echo "UMASK=0077" > /etc/initramfs-tools/conf.d/umask.conf
Because the encryption key is stored in the /etc/zfs
directory, it will automatically be copied into the initramfs.
Rebuild initramfs ...
# update-initramfs -c -k all
8. Install and configure ZFSBootMenu
8.1 Set ZFSBootMenu properties on datasets
Assign command-line arguments to be used when booting the kernel. Because ZFS properties are inherited, assign the common properties to the ROOT
dataset so all children datasets will inherit the arguments by default ...
# zfs set org.zfsbootmenu:commandline="quiet loglevel=4" zroot/ROOT
Setup key caching in ZFSBootMenu ...
# zfs set org.zfsbootmenu:keysource="zroot/ROOT/${ID}" zroot
To quickly discover and import pools on boot, set a pool cachefile
...
# zpool set cachefile=/etc/zfs/zpool.cache zroot
8.2 Install ZFSBootMenu
Fetch a prebuilt ZFSBootMenu EFI executable and save it to the EFI system partition ...
# mkdir -p /boot/efi/EFI/ZBM
# curl -o /boot/efi/EFI/ZBM/VMLINUZ.EFI -L https://get.zfsbootmenu.org/efi
# cp /boot/efi/EFI/ZBM/VMLINUZ.EFI /boot/efi/EFI/ZBM/VMLINUZ-BACKUP.EFI
8.3 Configure EFI boot entries
# mount -t efivarfs efivarfs /sys/firmware/efi/efivars
# efibootmgr -c -d "$BOOT_DISK" -p "$BOOT_PART" -L "ZFSBootMenu (Backup)" -l '\EFI\ZBM\VMLINUZ-BACKUP.EFI'
# efibootmgr -c -d "$BOOT_DISK" -p "$BOOT_PART" -L "ZFSBootMenu" -l '\EFI\ZBM\VMLINUZ.EFI'
9. Finish up
9.1 Exit the chroot and unmount
# exit
exit
# umount -n -R /mnt
9.2 Export the zpool and reboot
# zpool export zroot
# reboot
10. Resources
- ZFSBootMenu: Debian Bookworm (12) UEFI
- Gentoo Wiki: ZFS
- Encrypt a swap partition
- Minimal Debian (LUKS + LVM + ext4)
You can like, share, or comment on this post on Mastodon 💬
» Next: Roll your own Linux desktop using Sway
« Previous: Encrypt a swap partition