Set Up an Encrypted ZFS Pool
Configure native ZFS encryption with aes-256-gcm, manage keys and boot unlock, rotate keys safely, and replicate datasets encrypted with zfs send -w.
Before you start
- ▸Root or sudo access on the target system
- ▸OpenZFS 2.1 or later installed (2.2+ recommended)
- ▸One or more spare block devices or partitions — all data on them will be destroyed
- ▸Basic familiarity with ZFS pool and dataset concepts
ZFS native encryption, introduced in OpenZFS 0.8, lets you encrypt datasets or entire pools without a separate LUKS layer. The encryption lives inside the ZFS stack, so you keep all the usual ZFS features — snapshots, send/receive, compression — while your data stays ciphertext on disk. This guide walks through creating an encrypted pool, managing keys, and doing encrypted send/receive between hosts.
Prerequisites and Terminology
You need OpenZFS 2.1 or later for stable encryption support. OpenZFS 2.2+ fixes several edge cases around raw send; use it when possible. A few terms to keep straight:
- Encryption root: the dataset that owns a key. Child datasets inherit from it unless you set their own key.
- Keylocation / keyformat: how ZFS finds and interprets the key (passphrase, raw file, hex string).
- Raw send (
zfs send -w): sends encrypted ciphertext, not plaintext — the receiving host never sees unencrypted data.
Install OpenZFS
Debian / Ubuntu
sudo apt update
sudo apt install zfsutils-linux
Fedora / RHEL 9 / Rocky Linux 9
Enable the ZFS repo from openzfs.github.io, then:
sudo dnf install https://zfsonlinux.org/fedora/zfs-release-2-5$(rpm --eval "%{dist}").noarch.rpm
sudo dnf install zfs
Arch Linux
sudo pacman -S zfs-dkms zfs-utils
Confirm your version before continuing:
zfs version
Create an Encrypted Pool
The examples below use two drives (/dev/sdb and /dev/sdc) in a mirror. Adjust the vdev topology — stripe, raidz, single disk — as needed. All data on those drives will be destroyed.
Option A: Passphrase encryption at pool creation
sudo zpool create \
-O encryption=aes-256-gcm \
-O keyformat=passphrase \
-O keylocation=prompt \
-O compression=zstd \
-O atime=off \
tank mirror /dev/sdb /dev/sdc
ZFS will prompt for a passphrase twice. The pool and its root dataset are now the encryption root. Child datasets inherit the key automatically.
Option B: File-based key (for automated unlock)
Generate a 32-byte random key and store it somewhere safe — ideally on a separate encrypted volume or a secrets manager mount:
sudo dd if=/dev/urandom bs=32 count=1 | xxd -p -c 256 > /etc/zfs/tank.key
sudo chmod 400 /etc/zfs/tank.key
sudo zpool create \
-O encryption=aes-256-gcm \
-O keyformat=hex \
-O keylocation=file:///etc/zfs/tank.key \
-O compression=zstd \
-O atime=off \
tank mirror /dev/sdb /dev/sdc
Create Encrypted Datasets
The pool root dataset is already encrypted. Create child datasets as normal — they inherit the parent's encryption root and key:
sudo zfs create tank/data
sudo zfs create tank/backups
To give a child dataset its own independent key (so you can unmount and lock it separately):
sudo zfs create \
-o encryption=aes-256-gcm \
-o keyformat=passphrase \
-o keylocation=prompt \
tank/secrets
Verify the encryption root of each dataset:
sudo zfs get encryptionroot,encryption,keystatus tank tank/data tank/secrets
Load Keys and Mount on Boot
After a reboot, encrypted datasets are present but locked. You must load their keys before mounting.
Manual unlock
sudo zfs load-key tank
sudo zfs mount -a
Automatic unlock via systemd
For file-based keys, the ZFS utilities ship a systemd target and service you can hook into:
sudo systemctl enable zfs-import-cache.target
sudo systemctl enable zfs-mount.service
sudo systemctl enable zfs-load-key@tank.service
The zfs-load-key@ template unit calls zfs load-key <pool> at boot. It only works cleanly with keylocation=file://… or keylocation=https://…. For passphrase pools on a server, consider storing the passphrase in a systemd credential or a TPM-backed secret instead of a plain file.
Key Rotation and Changes
Rotate the key for an encryption root without re-encrypting the underlying data (ZFS re-wraps the data encryption key, not every block):
Change to a new passphrase
sudo zfs change-key -o keyformat=passphrase -o keylocation=prompt tank
Change to a new key file
sudo dd if=/dev/urandom bs=32 count=1 | xxd -p -c 256 > /etc/zfs/tank-new.key
sudo chmod 400 /etc/zfs/tank-new.key
sudo zfs change-key -o keyformat=hex -o keylocation=file:///etc/zfs/tank-new.key tank
Encrypted Send / Receive
Raw send (-w) transmits ciphertext. The receiving system never decrypts the data and does not need the key to store the stream. This is the correct way to replicate encrypted datasets to an untrusted backup host.
One-time full send
# On the source host:
sudo zfs snapshot tank/data@$(date +%Y%m%d)
sudo zfs send -w tank/data@20240601 | ssh backup-host sudo zfs receive -F backup/data
Incremental encrypted send
sudo zfs send -w -i tank/data@20240601 tank/data@20240602 \
| ssh backup-host sudo zfs receive -F backup/data
On the receiving host, the dataset will be present but locked — keystatus shows unavailable. That is expected and correct for an untrusted replica. To decrypt it there, you would run zfs load-key with the same key that was used on the source.
Important: You cannot mix raw and non-raw send streams for the same dataset. If you ever send without -w, the receiving dataset becomes a plaintext copy and loses its encrypted status. Always use -w for encrypted replication.
Verification
Check that encryption is active and the key is loaded:
sudo zfs get encryption,keylocation,keyformat,keystatus,encryptionroot tank
Expected keystatus is available when the key is loaded, unavailable when locked. Confirm data is actually ciphertext on disk by reading raw blocks from an unmounted, locked dataset — you should see no recognizable plaintext. Lock the dataset first:
sudo zfs unmount tank/data
sudo zfs unload-key tank/data
sudo dd if=/dev/sdb bs=512 count=8 skip=2048 2>/dev/null | strings | head
If the pool is encrypted, strings will return nothing meaningful.
Troubleshooting
- Key not found at boot: Verify the key file path is correct and readable by root before the ZFS mount service runs. If the file lives on another ZFS dataset, ordering matters — systemd unit dependencies get complex fast. Consider
keylocation=https://against a local secrets server instead. - Cannot receive: stream is not raw but target is encrypted: You sent without
-w. Destroy the receiving dataset, recreate it, and always usezfs send -w. - zfs: encryption not supported: Your kernel module is older than 0.8, or you built without encryption support. Run
modinfo zfs | grep versionand compare against the OpenZFS release notes. - Pool imports fine but datasets not mounted: Keys are not loaded. Run
zfs load-key -ato load all keys that have a reachablekeylocation, thenzfs mount -a. - Passphrase-based pool on a headless server won't auto-mount: This is by design. Use a file-based key stored securely, a TPM, or a network-bound disk encryption (NBDE) approach with Clevis/Tang.
Frequently asked questions
- Does ZFS native encryption protect against a stolen drive?
- Yes. With encryption=aes-256-gcm, all data blocks on disk are ciphertext. Without the key loaded, an attacker with physical access to the drive cannot read your data.
- Can I add encryption to an existing unencrypted pool?
- No. ZFS encryption must be set at dataset creation time. You must create a new encrypted dataset and copy or send data into it; there is no in-place encryption upgrade.
- Does raw send preserve deduplication tables?
- No. Raw send (-w) does not transmit dedup tables. The receiving pool deduplicates independently if dedup is enabled there, but the two tables are unrelated.
- What happens if I lose the key file?
- Your data is permanently unrecoverable. ZFS has no key escrow mechanism. Back up your key file to at least two separate secure locations, such as a password manager and offline encrypted storage.
- Can I use ZFS encryption alongside LUKS?
- Yes, and some high-security deployments layer both. LUKS protects at the block device level while ZFS encryption operates above it. The added complexity is only warranted in specific threat models; for most use cases ZFS native encryption alone is sufficient.
Related guides
AI and Artificial-Life Tools on Linux
Set up open-source AI/ML and artificial-life toolkits on Linux: PyTorch, JAX, DEAP, Avida, NetLogo, and RL environments with GPU driver guidance.
Assembly Language on Linux: A Starter Guide
Write x86-64 assembly on Linux from scratch: install NASM and GAS, learn syscalls, assemble and link a working program, then inspect and debug it.
How to Benchmark Disk Performance with fio
Learn to benchmark Linux disk performance with fio: writing job files, testing latency and throughput, and interpreting IOPS and percentile output correctly.
The Linux Boot Process Explained
Trace the full Linux boot sequence from UEFI firmware through GRUB2, the kernel, initramfs, and systemd to your login prompt — with diagnostics at each stage.