A step-by-step recipe for installing Debian 13 (Trixie) with a fully encrypted root filesystem using ZFS on top of LUKS.

Disclaimer: This process will nuke all data on the drives you target. Back up anything you want to keep before you start.

This gives you the integrity and snapshot features of ZFS, with the whole disk encrypted at rest. I use this on my home servers and VPS instances.


Prerequisites

  • Debian 13 Live ISO (the standard live image, not the netinst)
  • Two disks for a mirror, or one disk if you are starting small
  • UEFI boot (recommended for modern systems)

Why not just ZFS native encryption?

ZFS native encryption (zencrypt) exists and works. But LUKS gives you a battle-tested crypto layer that GRUB, initramfs, and every Linux tool understand natively. Combined with ZFS on top, you get the best of both worlds.


Step 1: Boot and prepare

Boot from the Debian 13 Live ISO and open a terminal.

sudo -i

Enable contrib and non-free for ZFS:

echo "deb http://deb.debian.org/debian trixie main contrib non-free non-free-firmware" > /etc/apt/sources.list
apt update

Install the essentials:

apt install --yes debootstrap gdisk sgdisk cryptsetup \
  zfs-dkms zfs-initramfs linux-headers-$(uname -r)

Load the ZFS module:

modprobe zfs

Step 2: Partition the disks

Clear any existing partition tables:

# Replace sdX and sdY with your disk IDs
sgdisk --zap-all /dev/sdX
sgdisk --zap-all /dev/sdY

Create the partitions on each disk. I will use sdX as the placeholder — repeat every command for sdY.

EFI partition (UEFI systems):

sgdisk -n1:1M:+512M -t1:EF00 /dev/sdX

Boot partition (unencrypted, for GRUB):

sgdisk -n2:0:+1G -t2:8300 /dev/sdX

LUKS data partition (rest of the disk):

sgdisk -n3:0:0 -t3:8300 /dev/sdX

Verify with:

sgdisk -p /dev/sdX

You should see three partitions: an EFI, a 1 GB boot, and a data partition filling the rest.


Step 3: Format the EFI and boot partitions

mkfs.fat -F32 /dev/sdX1
mkfs.fat -F32 /dev/sdY1

mkfs.ext4 /dev/sdX2
mkfs.ext4 /dev/sdY2

Step 4: Create the encrypted LUKS partition

Use aes-xts-plain64 with a 512-bit key on the third partition of each disk:

cryptsetup --cipher aes-xts-plain64 --key-size 512 \
  --verify-passphrase --hash sha512 --use-random \
  luksFormat /dev/sdX3

cryptsetup --cipher aes-xts-plain64 --key-size 512 \
  --verify-passphrase --hash sha512 --use-random \
  luksFormat /dev/sdY3

Open and map the encrypted devices:

cryptsetup luksOpen /dev/sdX3 root_crypt1
cryptsetup luksOpen /dev/sdY3 root_crypt2

Step 5: Create the ZFS pool

Create a mirrored ZFS pool on the mapped LUKS devices:

zpool create -o ashift=12 \
  -O atime=off -O canmount=off -O compression=lz4 \
  -O normalization=formD -O xattr=sa \
  -O mountpoint=/ -R /mnt \
  rpool mirror \
  /dev/disk/by-id/dm-name-root_crypt1 \
  /dev/disk/by-id/dm-name-root_crypt2

If using a single disk:

zpool create -o ashift=12 \
  -O atime=off -O canmount=off -O compression=lz4 \
  -O normalization=formD -O xattr=sa \
  -O mountpoint=/ -R /mnt \
  rpool /dev/disk/by-id/dm-name-root_crypt1

Step 6: Create ZFS datasets

# Container for root filesystem snapshots
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/debian
zfs mount rpool/ROOT/debian
zpool set bootfs=rpool/ROOT/debian rpool

# User data
zfs create -o setuid=off rpool/home
zfs create -o mountpoint=/root rpool/home/root

# System datasets
zfs create -o canmount=off -o setuid=off -o exec=off rpool/var
zfs create -o com.sun:auto-snapshot=false rpool/var/cache
zfs create rpool/var/log
zfs create rpool/var/spool
zfs create -o com.sun:auto-snapshot=false -o exec=on rpool/var/tmp

# Separate /tmp dataset
zfs create -o com.sun:auto-snapshot=false -o setuid=off rpool/tmp
chmod 1777 /mnt/tmp

Step 7: Install the base system

debootstrap trixie /mnt

Mount the boot partition and virtual filesystems:

mkdir /mnt/boot
mount /dev/sdX2 /mnt/boot
mount --rbind /dev /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys /mnt/sys
chroot /mnt /bin/bash --login

Step 8: Configure the installed system

Inside the chroot, mount /tmp and set up fstab:

mount /tmp
ln -s /proc/self/mounts /etc/mtab

Create /etc/fstab:

rpool/ROOT/debian / zfs defaults,noatime 0 0
/dev/sdX2         /boot ext4 defaults 0 0
/dev/sdX1         /boot/efi vfat defaults 0 0
tmpfs             /tmp tmpfs nosuid,nodev 0 0

Set up networking, hostname, and locales:

echo "deb http://deb.debian.org/debian trixie main contrib non-free non-free-firmware" > /etc/apt/sources.list
apt update
apt install --yes linux-image-amd64 grub-efi zfs-initramfs cryptsetup locales
dpkg-reconfigure locales     # ensure en_US.UTF-8 is selected
dpkg-reconfigure tzdata

Configure cryptsetup initramfs hook:

sed -i 's/#CRYPTSETUP=y/CRYPTSETUP=y/' /etc/cryptsetup-initramfs/conf-hook

Add the encrypted devices to crypttab:

echo "root_crypt1 /dev/disk/by-id/ata-YOURDISK-part3 none luks,discard" >> /etc/crypttab
echo "root_crypt2 /dev/disk/by-id/ata-YOURDISK-part3 none luks,discard" >> /etc/crypttab

Step 9: Install GRUB

# Enable GRUB to find ZFS
echo GRUB_PRELOAD_MODULES="part_gpt zfs" >> /etc/default/grub
echo GRUB_DISABLE_OS_PROBER=true >> /etc/default/grub

# Ensure ZFS pool devices are found by their /dev/disk/by-id names
echo "export ZPOOL_VDEV_NAME_PATH=YES" > /etc/profile.d/zpool_vdev_name.sh
ZPOOL_VDEV_NAME_PATH=YES update-grub

update-initramfs -u -k all

# Install to both disks for redundancy
grub-install /dev/sdX
grub-install /dev/sdY

Step 10: Exit and reboot

exit   # exit chroot
umount -R /mnt
zpool export rpool
cryptsetup luksClose root_crypt1
cryptsetup luksClose root_crypt2
reboot

On first boot, you will be prompted for the LUKS passphrase. After that, ZFS imports automatically and the system boots.

Next up: automatic unlock at boot

Typing a passphrase every time a server reboots gets old fast. See the next recipe to configure Tang + Clevis for password-less boot.


Credits and sources

This recipe builds on knowledge from several older guides that are still relevant: