| pick your distro, get ZFS on root
kldload — your platform, your way, free
Source

ZFS Encryption — per-dataset AES-256-GCM

ZFS native encryption is per-dataset, not full-disk. Each dataset can have its own key, its own cipher, and can be locked or unlocked independently. You can encrypt rpool/home without touching rpool/ROOT, or encrypt individual project datasets while leaving system files unencrypted. This is fundamentally different from LUKS.

ZFS encryption vs LUKS at a glance: LUKS encrypts a block device — everything on that partition is encrypted together, and the key must be available before the filesystem mounts at all. ZFS encrypts at the dataset level inside a live pool — you can load and unload keys per dataset while the system is running, snapshot encrypted data, and send encrypted streams to remotes without ever exposing plaintext.


How ZFS native encryption works

ZFS encryption uses AES-256-GCM (authenticated encryption with associated data) by default. Each block is encrypted with a unique IV derived from the block's DVA (data virtual address), so identical blocks at different offsets produce different ciphertext. The dataset key is itself wrapped with a "key encryption key" (KEK) derived from your passphrase via PBKDF2.

Metadata encryption is optional and off by default. Without it, file sizes, dataset structure, and access times are visible to someone with raw disk access — only content is hidden. Enable encryption=on with pbkdf2iters tuning for metadata protection if you need it.

Key properties you will set:

encryption

The cipher suite. Use aes-256-gcm (authenticated, fast on modern CPUs with AES-NI). aes-256-ccm is an alternative. Setting on uses the pool default.

keyformat

passphrase — entered interactively or via stdin.
raw — 32-byte random key from a file or stdin.
hex — 64-character hex string.

keylocation

Where ZFS looks for the key: prompt (stdin), file:///path/to/keyfile, or https:// URL. Can be changed after creation.

pbkdf2iters

PBKDF2 iteration count for passphrase stretching. Default is 350,000. Higher = slower key load but stronger brute-force resistance. Irrelevant for raw keys.


Creating an encrypted dataset

The most common case: a passphrase-protected dataset for sensitive data.

# Create an encrypted dataset — prompts for passphrase twice
zfs create \
  -o encryption=aes-256-gcm \
  -o keylocation=prompt \
  -o keyformat=passphrase \
  rpool/encrypted

# Create with a keyfile instead of a passphrase
dd if=/dev/urandom bs=32 count=1 of=/root/secrets/mykey.bin
zfs create \
  -o encryption=aes-256-gcm \
  -o keylocation=file:///root/secrets/mykey.bin \
  -o keyformat=raw \
  rpool/data/secrets

# Verify encryption is active
zfs get encryption,keyformat,keylocation,keystatus rpool/encrypted

Expected output from the get command:

NAME              PROPERTY     VALUE          SOURCE
rpool/encrypted   encryption   aes-256-gcm    local
rpool/encrypted   keyformat    passphrase     local
rpool/encrypted   keylocation  prompt         local
rpool/encrypted   keystatus    available      -

Child datasets inherit encryption from their parent. Once you create rpool/encrypted with a key, any zfs create rpool/encrypted/subdir is automatically encrypted with the same key — no flags needed.


Loading and unloading keys

A dataset with its key unloaded is inaccessible — it appears in zfs list but cannot be mounted. The data on disk is fully ciphertext. This is useful for datasets you only need periodically (backups, archives, sensitive projects).

# Unload a key — dataset becomes inaccessible
zfs unload-key rpool/encrypted

# Verify it's locked
zfs get keystatus rpool/encrypted
# NAME              PROPERTY   VALUE       SOURCE
# rpool/encrypted   keystatus  unavailable -

# Mount point disappears
ls /rpool/encrypted
# ls: cannot access '/rpool/encrypted': Input/output error

# Load the key again (prompts for passphrase)
zfs load-key rpool/encrypted

# Load and mount in one step
zfs load-key -a    # load ALL datasets with available key sources
zfs mount -a       # mount all loaded datasets

# Load key non-interactively (from stdin)
echo "mypassphrase" | zfs load-key rpool/encrypted

Tip: Use zfs load-key -a in a systemd unit or rc.local to load all dataset keys at boot from keyfiles. This avoids interactive passphrase prompts for non-root datasets while still protecting data at rest when the drive is removed.


Encrypted root dataset considerations

Encrypting the root dataset (rpool/ROOT/default or wherever your / lives) requires the key to be available before the system can boot. This is the main operational complexity of ZFS root encryption.

ZFSBootMenu handles this. When kldload installs with an encrypted root, ZFSBootMenu displays a passphrase prompt at boot — before the kernel even starts. It loads the key, imports the pool, and hands off to the OS. The flow is:

# During install — set an encrypted root at install time
# In the kldload web UI, enable "Encrypt root dataset" and enter a passphrase.
# The installer runs:
zpool create -o ashift=12 \
  -O encryption=aes-256-gcm \
  -O keylocation=prompt \
  -O keyformat=passphrase \
  -O compression=zstd \
  -O atime=off \
  rpool /dev/disk/by-id/...

# ZFSBootMenu will prompt for the passphrase at every boot.
# There is no way to boot unattended without a keyfile.

Warning: If you forget the passphrase for an encrypted root dataset, the data is gone. There is no recovery path. ZFS encryption is not like LUKS with a header backup — the master key is derived from your passphrase and there is no escrow. Store your passphrase in a password manager before you reboot.

For servers that need unattended boot, consider encrypting only data datasets (not root) and using keyfiles stored on a separate device or fetched from a key management server at startup.


Encrypted send/receive — raw send

ZFS can replicate encrypted datasets to a remote without ever decrypting them. The receiving host never sees the plaintext and does not need the key. This is called a raw send.

# Send an encrypted snapshot to a remote — plaintext never leaves this host
zfs snapshot rpool/encrypted@backup-$(date +%Y%m%d)

# Raw send — remote stores ciphertext, cannot read it without the key
zfs send --raw rpool/encrypted@backup-20260325 \
  | ssh backup-host zfs receive tank/offsite/encrypted

# Incremental raw send
zfs send --raw -i rpool/encrypted@backup-20260324 \
                  rpool/encrypted@backup-20260325 \
  | ssh backup-host zfs receive tank/offsite/encrypted

# Verify the remote dataset is encrypted (keystatus = unavailable means locked)
ssh backup-host zfs get keystatus tank/offsite/encrypted
# NAME                         PROPERTY   VALUE         SOURCE
# tank/offsite/encrypted       keystatus  unavailable   -

The backup host stores your data safely. Even if the backup host is compromised, an attacker gets only ciphertext. To restore, send the dataset back to a host with the key.

Important: You cannot mix raw and non-raw sends for the same dataset. If you send --raw to a remote, all subsequent incremental sends must also use --raw. Mixing them will fail with an error about incompatible stream types.


ZFS encryption vs LUKS

LUKS (block-level)

Encrypts an entire block device. Everything on the partition is protected. Key must be available before the filesystem is accessible at all — typically entered at GRUB or via a keyfile on initramfs. Standard on most Linux systems, well-supported by distro tools. All files share one key.

One lock on the front door. Everyone inside has full access.

ZFS native encryption (dataset-level)

Encrypts individual datasets. Different datasets can have different keys. Datasets can be locked/unlocked while the system is running. Compatible with ZFS snapshots, replication, and raw send. No separate dm-crypt layer.

Each room has its own lock. You can hand out keys selectively.

The main cases where LUKS is still the right choice: you want full-disk encryption including swap and hibernation on a non-ZFS system, or you need hardware TPM integration (TPM + LUKS unsealing is more mature than TPM + ZFS). For ZFS-on-root with selective dataset encryption, ZFS native is strictly better — no dm-crypt overhead, no separate keyfile management for the ZFS layer.


Changing a key or passphrase

# Change passphrase on an unlocked dataset
zfs change-key rpool/encrypted
# Prompts for new passphrase twice

# Change from passphrase to keyfile
zfs change-key \
  -o keylocation=file:///root/secrets/newkey.bin \
  -o keyformat=raw \
  rpool/encrypted

# Rewrap the master key (does not re-encrypt data, just rewraps the KEK)
# This is fast regardless of dataset size.

Key rotation rewraps the master key — it does not re-encrypt data blocks. The operation is instant even on multi-terabyte datasets. The old passphrase is immediately invalidated.


Quick reference

# Create encrypted dataset
zfs create -o encryption=aes-256-gcm -o keylocation=prompt -o keyformat=passphrase rpool/encrypted

# Check encryption status
zfs get encryption,keystatus rpool/encrypted

# Lock a dataset
zfs unload-key rpool/encrypted

# Unlock a dataset
zfs load-key rpool/encrypted && zfs mount rpool/encrypted

# Raw send to backup
zfs send --raw rpool/encrypted@snap | ssh remote zfs receive tank/backup

# Change passphrase
zfs change-key rpool/encrypted