ZFS Snapshots and Send/Receive
Create atomic ZFS snapshots, replicate datasets incrementally over SSH with zfs send/receive, and automate retention policies using zfs-auto-snapshot.
Before you start
- ▸A working ZFS pool on the source host (zpool status shows ONLINE)
- ▸SSH key-based authentication configured between source and backup host
- ▸Root or delegated ZFS permissions on both source and destination
- ▸mbuffer installed on both hosts for large transfers (optional but recommended)
ZFS snapshots are instantaneous, space-efficient, and crash-consistent. Combined with zfs send and zfs receive, they form a bulletproof replication pipeline that beats rsync for datasets where consistency matters. This guide walks through creating and managing snapshots, building incremental replication streams to a remote host, and automating retention with zfs-auto-snapshot.
How ZFS Snapshots Work
A snapshot captures the state of a dataset at a single point in time using copy-on-write. When you write new data, the original blocks are preserved for the snapshot; the snapshot itself costs only metadata until blocks are modified. Snapshots are read-only, cannot be accidentally overwritten, and are addressable as pool/dataset@snapshotname.
Because snapshots record the exact list of on-disk blocks at a moment in time, zfs send can serialize that block list into a byte stream. The receiving end reconstructs the dataset exactly — checksums and all — making this fundamentally different from file-level tools.
Prerequisites and Setup
You need ZFS installed on both source and destination hosts. On Debian/Ubuntu, ZFS on Linux ships in the official repos:
sudo apt install zfsutils-linux
On Fedora / RHEL / Rocky, enable the ZFS repository first:
sudo dnf install https://zfsonlinux.org/fedora/zfs-release-2-5$(rpm --eval "%{dist}").noarch.rpm
sudo dnf install zfs
sudo modprobe zfs
On Arch:
sudo pacman -S zfs-utils # requires archzfs or AUR; follow archzfs repo setup first
Verify your pool is healthy before doing anything else:
sudo zpool status
Creating Atomic Snapshots
A single dataset snapshot:
sudo zfs snapshot tank/data@2025-07-18_manual
Snapshot an entire pool hierarchy recursively with -r. This is atomic across all child datasets:
sudo zfs snapshot -r tank@2025-07-18_pre-upgrade
List snapshots:
zfs list -t snapshot -o name,creation,used,refer -s creation
Output will look similar to:
# NAME CREATION USED REFER
# tank/data@2025-07-18_manual Fri Jul 18 09:00 2025 0B 4.20G
# tank@2025-07-18_pre-upgrade Fri Jul 18 09:01 2025 0B 1.10G
Naming convention matters for automation. Use ISO-style timestamps (YYYY-MM-DD_HHMM) or a prefix that sorts lexicographically, so scripts can identify the oldest snapshot reliably.
Rolling Back and Destroying Snapshots
Roll a dataset back to a snapshot. This destroys any snapshots taken after the target unless you use -r to confirm:
sudo zfs rollback tank/data@2025-07-18_manual
Destroy a specific snapshot when you no longer need it:
sudo zfs destroy tank/data@2025-07-18_manual
Destroy a range of snapshots (ZFS 0.8+):
sudo zfs destroy tank/data@snap1%snap5 # destroys snap1 through snap5 inclusive
Replication with zfs send / zfs receive
Full Initial Send
Send a complete snapshot to a remote host over SSH. The -R flag includes all child datasets and their properties; -p preserves properties alone if you don't need recursion:
sudo zfs send -R tank/data@2025-07-18_manual \
| ssh backup-host sudo zfs receive -F backup/data
For large datasets, pipe through mbuffer to absorb network jitter and get transfer stats:
sudo zfs send -R tank/data@2025-07-18_manual \
| mbuffer -s 128k -m 1G \
| ssh backup-host "mbuffer -s 128k -m 1G | sudo zfs receive -F backup/data"
Incremental Replication
After the initial send, you only need to ship the difference between two snapshots. Create a new snapshot on the source:
sudo zfs snapshot tank/data@2025-07-19_daily
Send the increment from the previous snapshot to the new one:
sudo zfs send -R -i tank/data@2025-07-18_manual tank/data@2025-07-19_daily \
| ssh backup-host sudo zfs receive -F backup/data
The -i flag means "from this snapshot" (one specific parent). Use -I instead to include all intermediate snapshots if you missed a transfer cycle — this keeps the remote side's history intact.
Important: The parent snapshot referenced by -i must exist on both sides. If the remote is ever out of sync, use zfs list -t snapshot backup/data on the destination to confirm which snapshot to use as the base.
Scripted Nightly Replication
A minimal systemd service + timer pair is the right way to schedule this on modern systems. Create the script at /usr/local/bin/zfs-replicate.sh:
#!/usr/bin/env bash
set -euo pipefail
DATASET="tank/data"
REMOTE="backup-host"
REMOTE_DS="backup/data"
SNAP_NEW="${DATASET}@$(date +%Y-%m-%d_%H%M)"
SNAP_PREV=$(zfs list -t snapshot -H -o name -s creation "${DATASET}" | tail -2 | head -1)
zfs snapshot "${SNAP_NEW}"
zfs send -R -i "${SNAP_PREV}" "${SNAP_NEW}" \
| ssh "${REMOTE}" zfs receive -F "${REMOTE_DS}"
sudo chmod +x /usr/local/bin/zfs-replicate.sh
Create the systemd unit at /etc/systemd/system/zfs-replicate.service:
[Unit]
Description=ZFS incremental replication to backup-host
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/zfs-replicate.sh
User=root
And the timer at /etc/systemd/system/zfs-replicate.timer:
[Unit]
Description=Run ZFS replication nightly
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now zfs-replicate.timer
Automated Retention with zfs-auto-snapshot
zfs-auto-snapshot creates and expires snapshots on a configurable schedule. It integrates with cron or systemd and is available on all major distros.
# Debian/Ubuntu
sudo apt install zfs-auto-snapshot
# Fedora / RHEL — install from EPEL or clone from GitHub
sudo dnf install epel-release && sudo dnf install zfs-auto-snapshot
# Arch (AUR)
yay -S zfs-auto-snapshot
After installation, it ships cron jobs in /etc/cron.d/zfs-auto-snapshot and /etc/cron.{hourly,daily,weekly,monthly}/. The defaults keep 4 frequent (15-min), 24 hourly, 31 daily, 8 weekly, and 12 monthly snapshots.
Control which datasets participate with the com.sun:auto-snapshot property:
# Enable on a dataset
sudo zfs set com.sun:auto-snapshot=true tank/data
# Disable for a child dataset you want to exclude (e.g., a scratch volume)
sudo zfs set com.sun:auto-snapshot=false tank/scratch
To override retention per schedule, pass flags directly. For example, to keep only 7 daily snapshots on a specific dataset:
sudo zfs-auto-snapshot --keep=7 --label=daily --quiet tank/data
If you prefer systemd timers over cron, disable the cron jobs and create timers following the same pattern shown in the replication section above, calling zfs-auto-snapshot as the ExecStart.
Verification
Confirm replication arrived intact by comparing the most recent snapshot name on both sides:
# Source
zfs list -t snapshot -H -o name -s creation tank/data | tail -1
# Destination (run on backup-host or via SSH)
ssh backup-host zfs list -t snapshot -H -o name -s creation backup/data | tail -1
For a deeper check, compare the written and used properties, or use zfs diff between snapshots to inspect exactly what changed:
zfs diff tank/data@2025-07-18_manual tank/data@2025-07-19_daily
Troubleshooting
- "cannot receive incremental stream: most recent snapshot does not match incremental source" — The remote is ahead of or behind your chosen base. Run
zfs list -t snapshoton both sides and find a common snapshot to use as the-iargument. - Send is unexpectedly large — Check whether the dataset has
dedupenabled on the source but not the destination; dedup ratios don't transfer. Also verify you're using-i(single increment) not-I(all intermediates) unless you need history replay. - zfs-auto-snapshot not creating snapshots — Confirm
com.sun:auto-snapshot=trueis set and cron / the timer is active:systemctl list-timersorgrep auto-snapshot /var/log/syslog. - Permission denied on receive — The remote user needs
zfs allowdelegation or must run as root. Grant delegated permissions withsudo zfs allow -u backupuser receive,create,mount backup.
Frequently asked questions
- How much disk space do ZFS snapshots consume?
- A freshly created snapshot uses almost no space. Space is consumed only as the live dataset diverges from the snapshot — each overwritten block is retained for the snapshot. Run 'zfs list -t snapshot -o name,used' to see actual consumption per snapshot.
- Can I send a snapshot to a pool with different compression or recordsize settings?
- Yes, but with a caveat. zfs send -R preserves the source dataset's properties by default, which will be applied on receive. If you want the destination's settings to take precedence, omit -p/-R and set properties manually on the destination after the initial receive.
- What happens if a replication job fails mid-stream?
- The destination will have a partial or incomplete receive. Use 'zfs receive -F' on the next attempt to force a rollback to the last good snapshot before resuming, or use 'zfs send -t' with resume tokens (ZFS 0.8+) to continue an interrupted transfer without restarting.
- Is there a way to replicate without root access on the remote?
- Yes. Use 'zfs allow -u backupuser send,snapshot pool/dataset' on the source and 'zfs allow -u backupuser receive,create,mount,rollback pool' on the destination. The delegated user can then run send/receive without sudo.
- Does zfs-auto-snapshot work with encrypted datasets?
- Yes, snapshots of encrypted datasets are themselves encrypted and can be sent in raw form using 'zfs send --raw'. The destination must have the same or a compatible ZFS version, and you'll need to load the encryption key on the destination to access the data.
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.