ZFS on Root on Debian (deep dive)
Install Debian 12 with ZFS on root: partition layout, boot pool vs root pool, dataset hierarchy, GRUB configuration, and first-boot verification.
Before you start
- ▸Debian 12 live ISO written to USB and booted on the target machine
- ▸UEFI firmware with Secure Boot disabled or MOK key enrolled
- ▸Target disk(s) identified and confirmed expendable — all data will be destroyed
- ▸Network connectivity from the live session for package downloads
Installing Debian with ZFS as the root filesystem is not a checkbox in the standard installer — it requires manual partitioning, building a boot environment from a live session, and understanding how ZFS pools, datasets, and GRUB interact. This guide follows the OpenZFS-on-Linux approach that the Debian wiki recommends, adapted for Debian 12 (Bookworm). Expect to spend 60–90 minutes the first time.
Why ZFS on Root?
ZFS gives you copy-on-write snapshots, built-in checksumming, dataset-level quotas, and trivial boot environment cloning. The tradeoff: the setup is complex, ZFS is not in the mainline kernel (it ships via DKMS on Debian), and pool/dataset layout decisions made at install time are painful to undo. Get the layout right from the start.
Concepts to Understand First
Boot pool vs. root pool
GRUB can read ZFS, but only a limited subset of ZFS features. If your root pool uses features GRUB does not understand, the system will not boot. The standard solution is two pools:
- bpool — a small pool (<2 GB) using only features GRUB supports. Holds
/boot. - rpool — the main pool with all modern features enabled. Holds everything else.
This split is not optional if you want ZFS compression, encryption, or features introduced after GRUB's ZFS support froze. The bpool must be created with -d (disable all features) plus explicit -o feature@async_destroy=enabled and a short allow-list.
Dataset hierarchy
ZFS root installs work best with a structured dataset tree rather than one flat dataset. A typical layout:
rpool/ROOT/debian— mounted at/rpool/home— mounted at/homerpool/var/log,rpool/var/cache— separate so snapshots of ROOT do not capture ephemeral databpool/BOOT/debian— mounted at/boot
Prerequisites
- Debian 12 live ISO (the standard or netinst live image works; avoid the minimal non-live installer)
- Target disk(s) identified — this guide uses
/dev/sdafor a single-disk setup and notes where to mirror - UEFI firmware (GPT + EFI partition); the guide notes BIOS/MBR differences where relevant
- Network access from the live environment for package installation
Step 1: Prepare the Live Environment
Boot the Debian live ISO. Open a terminal and gain root:
sudo -i
Install the ZFS utilities and DKMS module into the live session:
apt update
apt install --yes debootstrap gdisk zfsutils-linux
Confirm the ZFS module loaded:
modprobe zfs
lsmod | grep zfs
Step 2: Partition the Disk
Wipe the target disk and create a GPT partition table. Adjust sizes to your disk. This layout assumes UEFI:
DISK=/dev/sda
sgdisk --zap-all $DISK
sgdisk -n 1:0:+1G -t 1:EF00 -c 1:"EFI" $DISK # EFI System Partition
sgdisk -n 2:0:+2G -t 2:BE00 -c 2:"bpool" $DISK # ZFS boot pool
sgdisk -n 3:0:0 -t 3:BF00 -c 3:"rpool" $DISK # ZFS root pool
partprobe $DISK
For BIOS/MBR systems, replace the EFI partition with a 1 MB BIOS boot partition (type EF02) and adjust GRUB installation later.
Step 3: Create the ZFS Pools
Boot pool (bpool)
The -d flag disables all features; only those explicitly listed are enabled — keeping the pool within GRUB's understanding:
zpool create \
-o ashift=12 \
-o autotrim=on \
-d \
-o feature@async_destroy=enabled \
-o feature@bookmarks=enabled \
-o feature@embedded_data=enabled \
-o feature@empty_bpobj=enabled \
-o feature@enabled_txg=enabled \
-o feature@extensible_dataset=enabled \
-o feature@filesystem_limits=enabled \
-o feature@hole_birth=enabled \
-o feature@large_blocks=enabled \
-o feature@lz4_compress=enabled \
-o feature@spacemap_histogram=enabled \
-O acltype=posixacl \
-O canmount=off \
-O compression=lz4 \
-O devices=off \
-O normalization=formD \
-O relatime=on \
-O xattr=sa \
-O mountpoint=/boot \
-R /mnt \
bpool ${DISK}2
Root pool (rpool)
The root pool gets all modern features. Add -O encryption=aes-256-gcm -O keylocation=prompt -O keyformat=passphrase before -O mountpoint=/ if you want native ZFS encryption:
zpool create \
-o ashift=12 \
-o autotrim=on \
-O acltype=posixacl \
-O canmount=off \
-O compression=lz4 \
-O dnodesize=auto \
-O normalization=formD \
-O relatime=on \
-O xattr=sa \
-O mountpoint=/ \
-R /mnt \
rpool ${DISK}3
For a mirrored pair, replace ${DISK}3 with mirror ${DISK}3 /dev/sdb3 (mirror both pools similarly).
Step 4: Create Datasets
# Root pool containers
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/debian
zpool set bootfs=rpool/ROOT/debian rpool
# Boot pool
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
zfs create -o canmount=noauto -o mountpoint=/boot bpool/BOOT/debian
# Mount root and boot
zfs mount rpool/ROOT/debian
zfs mount bpool/BOOT/debian
# Additional datasets
zfs create -o mountpoint=/home rpool/home
zfs create -o mountpoint=/root rpool/home/root
chmod 700 /mnt/root
zfs create -o canmount=off -o mountpoint=/var rpool/var
zfs create -o canmount=off rpool/var/lib
zfs create rpool/var/log
zfs create rpool/var/cache
zfs create rpool/var/spool
zfs create -o com.sun:auto-snapshot=false rpool/var/cache
zfs create rpool/tmp
chmod 1777 /mnt/tmp
Step 5: Install Debian with debootstrap
debootstrap bookworm /mnt
# Write pool cache so the initramfs can import pools
mkdir -p /mnt/etc/zfs
zpool set cachefile=/etc/zfs/zpool.cache bpool
zpool set cachefile=/etc/zfs/zpool.cache rpool
cp /etc/zfs/zpool.cache /mnt/etc/zfs/
Bind-mount the virtual filesystems and chroot:
mount --make-private --rbind /dev /mnt/dev
mount --make-private --rbind /proc /mnt/proc
mount --make-private --rbind /sys /mnt/sys
chroot /mnt /usr/bin/env \
DISK=$DISK \
bash --login
Step 6: Configure the Chroot Environment
# Hostname and basic config
echo "myhostname" > /etc/hostname
echo "127.0.1.1 myhostname" >> /etc/hosts
# /etc/fstab — EFI only; ZFS mounts itself
cat > /etc/fstab <<'EOF'
#
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
EOF
# Locales and timezone
apt install --yes locales
dpkg-reconfigure locales
dpkg-reconfigure tzdata
# Set root password
passwd
Add the contrib component (required for ZFS DKMS on Debian) and install the kernel and ZFS packages:
apt install --yes software-properties-common
apt-add-repository contrib
apt update
apt install --yes linux-image-amd64 linux-headers-amd64 \
zfs-initramfs zfs-dkms dosfstools
Step 7: Install and Configure GRUB
Format and mount the EFI partition, then install GRUB:
mkdosfs -F 32 -s 1 -n EFI ${DISK}1
mkdir -p /boot/efi
echo "${DISK}1 /boot/efi vfat umask=0022,fmask=0133 0 0" >> /etc/fstab
mount /boot/efi
apt install --yes grub-efi-amd64 grub-efi-amd64-signed shim-signed
Set GRUB to recognize ZFS and update configuration:
echo 'GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/debian"' >> /etc/default/grub
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
--bootloader-id=debian --recheck --no-floppy
For BIOS systems, use grub-install ${DISK} with the grub-pc package instead.
Step 8: Finalize and First Boot
# Snapshot clean install state
zfs snapshot bpool/BOOT/debian@install
zfs snapshot rpool/ROOT/debian@install
# Enable ZFS services
systemctl enable zfs-import-cache zfs-import.target zfs-mount zfs-share zfs-zed
# Exit chroot, unmount, export pools
exit
mount | grep -v zfs | tac | awk '/\/mnt/{print $3}' | xargs -I{} umount -lf {}
zpool export -a
Remove the live ISO and reboot. GRUB should appear and boot into Debian from rpool/ROOT/debian.
Verification
After the first successful boot, confirm both pools are imported and healthy:
zpool status
zpool list
zfs list -t all
Output will vary by hardware, but HEALTH should read ONLINE for both bpool and rpool. Verify the boot dataset:
zpool get bootfs rpool
Troubleshooting
GRUB drops to rescue shell
This almost always means GRUB cannot find or read the ZFS pool. Boot the live ISO, import the pools (zpool import -f -R /mnt rpool bpool), chroot back in, and run update-grub again. Double-check that bpool was created with -d and only the allowed feature set.
initramfs cannot import rpool
The zpool.cache file is missing or stale in the initramfs. Inside the chroot run zpool set cachefile=/etc/zfs/zpool.cache rpool bpool, then update-initramfs -u -k all.
ZFS DKMS module fails to build
Ensure linux-headers-amd64 matches your running kernel version exactly. Run dkms status to see the current state and dkms build zfs/$(dkms status | grep zfs | awk '{print $2}') to retry manually.
Pool imported read-only on boot
Usually caused by an unclean export. Boot the live ISO, run zpool import -f rpool, export cleanly with zpool export rpool, and reboot.
Frequently asked questions
- Can I use native ZFS encryption on the root pool?
- Yes. Add -O encryption=aes-256-gcm -O keylocation=prompt -O keyformat=passphrase when creating rpool. The bpool should remain unencrypted so GRUB can read /boot without a pre-boot unlock mechanism.
- Why does bpool need a restricted feature set?
- GRUB's ZFS implementation is frozen at a point that predates many modern ZFS features. If bpool uses features GRUB cannot parse, GRUB cannot read the filesystem and will drop to a rescue shell.
- Can I add a second disk for mirroring later?
- Yes, but it is far easier to set up mirrors at install time. To add a mirror vdev later, use zpool attach rpool /dev/sda3 /dev/sdb3 (and the same for bpool). Resilvering takes time proportional to used space.
- How do I create and boot a new boot environment after a major upgrade?
- Snapshot rpool/ROOT/debian, clone it to rpool/ROOT/debian-new, update /etc/grub.d or use a tool like zfsbootmenu, run update-grub, and reboot. If the new environment is broken, simply set bootfs back to the previous dataset.
- Does this setup work with Secure Boot?
- The grub-efi-amd64-signed and shim-signed packages included in Step 7 support Secure Boot. However, the ZFS DKMS module is not signed by default, which can cause failures with strict Secure Boot MOK policies. You must either enroll your own MOK key or disable Secure Boot.
Related guides
Alpine Linux on Servers and in Containers
Deploy Alpine Linux on servers and in containers: musl vs glibc trade-offs, apk package management, OpenRC init, security hardening, and container best practices.
Arch vs EndeavourOS vs Manjaro
Arch, EndeavourOS, and Manjaro compared honestly: repository differences, AUR compatibility, install experience, and which one suits your actual workflow.
The Best Linux Distros for Beginners
The best Linux distros for beginners in 2024: Ubuntu, Linux Mint, Fedora, and Pop!_OS compared with honest pros, cons, and setup tips.
The Best Linux Distros for Servers
Compare Ubuntu LTS, Debian, AlmaLinux, Rocky Linux, RHEL, Arch, and SLES for server use: support lifecycles, stability trade-offs, and how to choose the right fit.