Skip to content
FreeBSDHow-To Published Updated 3 min readViews unavailable

How to Replicate FreeBSD ZFS Datasets Safely with send and receive

A recovery-oriented ZFS replication workflow covering recursive snapshots, incremental ancestry, receive safety, resumable streams, holds, and audits.

zfs send serializes a snapshot into a ZFS stream; zfs receive reconstructs that state in another dataset. This preserves ZFS properties, snapshots, clones, and—in supported modes—raw encrypted blocks more faithfully than a file-tree copy. It is still not automatically a backup: one bad recursive destination, deleted common snapshot, or untested receive can destroy the recovery path.

Design source and destination namespaces

Use a dedicated destination pool or host and a predictable dataset root. Never experiment with a receive against the only production copy. Confirm pool health, capacity, feature compatibility, encryption requirements, and which properties may be inherited at the destination.

zpool status -x
zfs list -o name,used,available,mountpoint,encryptionroot

A receiving system must support every stream feature it is asked to import. A raw encrypted send can preserve ciphertext and avoid exposing the key on the transport host, but raw, replicated, and embedded-block options have exact version and dataset constraints. Read the installed zfs-send(8) and zfs-receive(8) pages before selecting flags.

Establish one named recovery point

For a hierarchy, take recursive snapshots so child datasets share one suffix:

SNAP=replica-2026-08-09T120000Z
zfs snapshot -r tank/services@"$SNAP"

A recursive snapshot is coordinated across the named dataset tree, but it does not flush application transactions by magic. Quiesce databases through their supported backup interface, take application-native backups, or prove crash consistency. Record exactly what was paused and when.

For a first transfer, preview the size and stream plan before receiving:

zfs send -nPv tank/services@"$SNAP"

Then send to a destination dataset whose name you have verified:

zfs send tank/services@"$SNAP" | \
  ssh backup.example zfs receive -u backup/services

-u avoids mounting the received dataset immediately. This reduces the chance that a recovery copy collides with production mount points or starts serving stale data.

Incrementals require shared ancestry

An incremental stream describes changes between related snapshots. Both sides must retain the base snapshot:

zfs send -i tank/services@replica-old \
  tank/services@replica-new | \
  ssh backup.example zfs receive -u backup/services

-I can include intermediate snapshots, while replication flags can include descendants and properties. Those conveniences increase the receive scope. Inspect with dry-run/verbose options and test on a disposable destination before using recursive or forced receives.

Protect required base snapshots with holds so routine pruning cannot delete them:

zfs hold replication tank/services@replica-old
zfs holds tank/services@replica-old

A hold prevents deletion of that snapshot on that system; it does not replicate policy intent or prevent destruction of the pool.

Treat destructive receive flags as change control

zfs receive -F can roll back the destination to match the stream, destroying newer destination changes. Recursive replication with additional destructive flags can remove destination snapshots or datasets absent from the source. Never add them just because a receive reports divergence.

Determine why histories diverged: an operator wrote to the backup, retention removed a base, the wrong source was selected, or a previous receive stopped. Preserve the destination state before reconciling. If the installed OpenZFS version supports resumable receive, capture the resume token and use zfs send -t rather than restarting a multi-terabyte transfer without analysis.

Verify the replica as a recoverable system

Compare snapshot inventories, dataset properties, encryption roots, used/logical sizes, and expected child datasets:

zfs list -r -t filesystem,snapshot backup/services
zfs get -r -s local,received all backup/services

Mount a clone or isolated received dataset read-only, restore application data, and run checks at the application layer. A successful pipeline exit only proves bytes moved through the commands; without pipefail or explicit remote status handling, it may not even prove both sides succeeded.

Monitor last successful snapshot, last completed receive, lag, capacity forecast, pool health, checksum errors, and pruning. Keep an offline or independently administered copy so compromised source credentials cannot erase every generation. Replication is complete when an operator can restore the service from the destination without consulting the failed source.

Related:

Sources:

Comments