Synopsis #
ZFS send streams can cross an SSH connection without storing an intermediate archive. The difficult part is authorization: the source needs permission to send the intended snapshot, while the destination needs narrowly scoped permission to receive into one dataset. Granting unrestricted remote root access merely to simplify a backup expands the failure boundary.
This guide defines the design and verification sequence for an unencrypted dataset. It intentionally does not supply a universal authorized_keys command wrapper because dataset names, retention, direction, and the permitted receive forms determine which operations are safe.
Establish the local replication chain first #
Complete one local full receive and one incremental receive using Snapshot and replicate a ZFS dataset . Remote transport should not be introduced until snapshot naming, destination layout, and restore verification work locally.
Record:
- source and destination datasets;
- snapshot naming and retention policy;
- whether child datasets are included;
- whether encryption must remain raw;
- the oldest common snapshot required for incrementals;
- behavior after an interrupted transfer.
Create a dedicated destination boundary #
Use a dedicated account and an existing parent dataset for the sender. Do not reuse an administrator’s interactive key. ZFS delegation with zfs allow can grant operations on that parent and its descendants, but each permission should be derived from the exact receive workflow and tested against a disposable dataset.
For example, create a parent that contains received children but does not itself hold replicated data:
# zfs create -o canmount=off -o mountpoint=none backup/replica
$ zfs allow backup/replica
The first full stream in this guide creates backup/replica/data. That child must not exist before the full receive. Keeping the delegated parent separate from the received child makes the creation boundary explicit and avoids trying to receive a full stream into the already existing delegation root.
On the destination, keep the received dataset unmounted by default or assign a non-conflicting mount point. A replica should not shadow production data merely because it arrived successfully.
Inspect existing delegation before adding any:
$ zfs allow backup/replica
The destination account may need receive-related permissions and the ability to create descendants under the selected parent. It should not receive pool-wide administration or ZFS access to unrelated datasets.
The examples below use an ordinary SSH login and shell so that the supplied zfs receive command can run. Dataset delegation limits what ZFS operations that account can perform, but it does not restrict the SSH key to one remote command. A compromised source can invoke any command available to the destination account. A forced-command wrapper or an sshd_config restriction is a separate control that must be implemented and tested before describing this path as command-restricted.
Exclude encrypted and raw streams from this procedure #
Confirm the source dataset’s encryption state:
$ zfs get encryption,encryptionroot tank/data
Proceed with these command examples only when encryption is off. For an encrypted dataset, zfs send -w creates a raw stream that preserves the encrypted on-disk representation and encryption parameters. A raw stream must be received as-is: the receive cannot decrypt, re-encrypt, or recompress it. Mixing raw and non-raw receive history also affects whether later raw incrementals can be accepted.
Raw replication therefore needs its own reviewed full-and-incremental chain, destination key-recovery test, and consistent use of -w. Adding -w to only one command in this procedure is not a safe adaptation.
Estimate and send the first stream #
On the source, estimate the stream:
# zfs send -nPv tank/data@replica-1
Then send it to the reviewed remote receive command:
# zfs send -v tank/data@replica-1 | ssh backup@backup.example.org zfs receive -u -s backup/replica/data
-u leaves the received dataset unmounted. -s preserves resumable receive state if the stream is interrupted. The SSH host key must be verified through an independent channel before automating the connection.
Confirm the received snapshot on the destination:
$ zfs list -t snapshot backup/replica/data@replica-1
Send an incremental snapshot #
After creating the next source snapshot, estimate the incremental difference:
# zfs send -nPv -i tank/data@replica-1 tank/data@replica-2
Send it only after confirming that replica-1 remains on both sides:
# zfs send -v -i tank/data@replica-1 tank/data@replica-2 | ssh backup@backup.example.org zfs receive -u -s backup/replica/data
Destroying the last common snapshot before a successful receive breaks the incremental path. Retention should remove snapshots only after destination verification.
Handle interruption without guessing #
When a receive started with -s is interrupted, the destination dataset can expose a receive_resume_token:
$ zfs get receive_resume_token backup/replica/data
The corresponding resume workflow uses zfs send -t with that token. Treat the token as state from the destination and use the exact current manual-page procedure. Do not begin a new full receive over a partial destination without deciding whether to resume or abort the saved state.
Verify recovery, not only transfer #
At intervals, receive or clone the replicated snapshot into an isolated recovery namespace and validate representative data and application consistency. Monitor:
- last successful snapshot on both sides;
- destination capacity and snapshot growth;
- SSH and receive failures;
- pool health at source and destination;
- retention execution;
- time since the last restore test.