Skip to content
LinuxDeep Dive Published Updated 8 min readViews unavailable

Btrfs Send and Receive: Snapshot-Based Replication with Verifiable Baselines

Build Btrfs send/receive replication around read-only snapshots, verified incremental parents, transport integrity, receiver policy, and restore tests.

Btrfs send and receive transfer subvolume snapshots as a structured stream. A full send describes a snapshot without an incremental parent; an incremental send describes changes relative to a parent snapshot that the receiver must already possess. This makes replication efficient, but it also makes the parent relationship part of the backup protocol. A stream that cannot be applied to the expected baseline is not a valid incremental chain, and a successful receive is not proof that the data is restorable or application-consistent.

Use Btrfs send/receive when source and destination filesystems can exchange the Btrfs stream format and you can manage snapshots and lineage explicitly. It is not a general file copy: the receiver creates a subvolume representation of the sent snapshot, and stream semantics include metadata and filesystem-specific behavior. Treat the destination as an independent replica, protect it from accidental source-side deletion, and test restores on the actual receiving filesystem and kernel/tool versions.

Model snapshot identity and chain state

Create read-only snapshots for send operations. A read-only snapshot provides a stable source state and is required for normal send workflows. Record the source subvolume, snapshot name, creation time, and parent snapshot identity. Incremental mode uses a previously sent snapshot as the parent and the newer snapshot as the send source. The receiver must have the corresponding parent state; using a same-named but different snapshot is not enough.

Snapshot names are operator labels, not cryptographic identities. Maintain an inventory that maps each snapshot to the source subvolume and lineage. Verify read-only state and snapshot relationships before generating an incremental stream:

sudo btrfs subvolume list -t -o /srv/data
sudo btrfs property get -t subvolume /srv/data/.snapshots/daily-2026-10-03 ro
sudo btrfs property get -t subvolume /srv/data/.snapshots/daily-2026-10-04 ro

The paths here are examples. The ro property should be true for send snapshots. btrfs subvolume list -o is also important because snapshots are not recursive: nested subvolumes under /srv/data are represented as empty subvolume stubs in a parent snapshot and are not included as ordinary file trees. Replicate each nested subvolume separately or redesign the layout, then record each stream’s lineage. Confirm that the newer snapshot was created from the expected prior snapshot or has a valid incremental relationship. Do not infer lineage from directory names alone, especially after moving, replacing, or recreating snapshots.

Create and transfer a full baseline

Assume /srv/data is the source subvolume and /srv/.snapshots is a separate directory on the same Btrfs filesystem, outside the source subvolume. Create that snapshot directory first and then a read-only snapshot with a unique name:

sudo mkdir -p /srv/.snapshots
sudo btrfs subvolume snapshot -r /srv/data /srv/.snapshots/full-2026-10-04
sudo btrfs property get -ts /srv/.snapshots/full-2026-10-04 ro

The source and destination must be on the same Btrfs filesystem, while the destination path must not be inside the source subvolume. In production, use a sibling snapshot root or dedicated snapshot subvolume. Check findmnt -T for both paths, btrfs subvolume list, and your layout before creating paths; a plain directory on another filesystem or inside the source is not an equivalent destination.

For a full transfer over SSH, stream to the receiving host and receive into a directory on a Btrfs filesystem:

sudo btrfs send /srv/.snapshots/full-2026-10-04 \
  | ssh backup.example 'sudo btrfs receive /backup/btrfs'

The receiving directory must exist and be on Btrfs; btrfs receive creates a received subvolume there. Ensure the remote account, privilege escalation, storage capacity, and SSH host identity are configured before relying on unattended transfers. The example uses a host name and paths as placeholders. A shell pipeline’s exit status can hide an upstream failure unless the shell uses pipefail; in automation, check both the sender and receiver status and preserve logs.

For a reproducible pipeline in Bash, use:

set -o pipefail
sudo btrfs send /srv/.snapshots/full-2026-10-04 \
  | ssh backup.example 'sudo btrfs receive /backup/btrfs'

Confirm that the receive created the expected subvolume and that it is listed on the receiver. Do not edit a received snapshot as if it were an ordinary writable working tree; preserve the replica baseline and create a separate writable snapshot or clone for restore testing.

Send an incremental snapshot only from a known parent

After the receiver has the baseline, create a newer read-only snapshot from the same source subvolume with the correct lineage. For example:

sudo btrfs subvolume snapshot -r /srv/data /srv/.snapshots/daily-2026-10-05
sudo btrfs property get -ts /srv/.snapshots/daily-2026-10-05 ro

Then send it relative to the last successfully received baseline:

set -o pipefail
sudo btrfs send -p /srv/.snapshots/full-2026-10-04 \
  /srv/.snapshots/daily-2026-10-05 \
  | ssh backup.example 'sudo btrfs receive /backup/btrfs'

The -p option identifies the parent snapshot. The receiver must contain the corresponding parent state from which the incremental stream was generated. If a transfer fails, do not advance the recorded baseline merely because the sender started or the SSH session ended. Verify receiver state and the exit status before marking the child snapshot replicated.

For periodic jobs, persist a small replication manifest outside the stream: source subvolume identity, snapshot ID/name, parent ID/name, send start/end, sender/receiver versions, exit status, and verification result. Make the job idempotent at the orchestration layer. A retry may encounter a partially received or already received snapshot; inspect receiver state and use documented recovery procedures rather than deleting a destination subvolume automatically.

Protect the receiver and the stream

The stream is a data-transfer format, not by itself a security boundary. Authenticate and validate the SSH endpoint, restrict the receiving account to the intended operation where possible, and ensure receiver-side paths cannot be redirected into unrelated subvolumes. Backups should have a separate administrative boundary from the source host. If the source’s credentials can erase every retained replica, the design does not provide meaningful protection from source compromise or operator error.

Monitor free space, quotas, snapshot retention, and receive errors. Snapshots share extents until data changes, but replication and retention still consume capacity. Do not assume that deleting a snapshot immediately reclaims all of its referenced space. Inspect subvolume references and filesystem usage with appropriate Btrfs tools, and reserve capacity for metadata and incoming changes.

Transport compression or SSH compression can shift CPU and bandwidth costs; test with representative data rather than assuming it helps. A local pipe between btrfs send and btrfs receive can avoid an archive file, while storing the stream as a file provides a separately manageable artifact but requires integrity and retention controls. If persisting a stream, record a checksum and verify it before receive; a checksum proves byte identity, not that the source snapshot was application-consistent.

Application consistency and recovery

A filesystem snapshot captures filesystem state, not necessarily a consistent transaction boundary for every application. Databases may require their own backup mode, checkpoint, quiesce hook, or log shipping. Coordinate the snapshot with the application and test recovery from the replica. A snapshot taken during active writes can be crash-consistent rather than application-consistent; the distinction must be part of the recovery objective.

Test recovery by selecting a known snapshot, receiving it into an isolated location, creating a writable test subvolume if needed, and validating application data with the application’s own integrity checks. Do not restore over the only production copy as a test. A successful btrfs receive confirms stream application, not application correctness, permissions at the service boundary, or complete disaster recovery.

Btrfs received subvolumes preserve snapshot data and receive metadata, but their filesystem identity and behavior should not be treated as a byte-for-byte clone of the source subvolume object. The receiver assigns its own subvolume UUID while recording the source identity as received metadata; avoid tooling that assumes those IDs are interchangeable. Btrfs receive also documents risks from specially crafted streams, including reflinking files elsewhere on the receiving filesystem. Receive only trusted streams into a dedicated filesystem or isolated destination, and test the exact receiver policy before accepting data from an untrusted sender.

Operational checks and acceptance criteria

Before every incremental run, verify source snapshot read-only state, parent availability at both ends, destination capacity, and the last known good replication manifest. After the transfer, check command exit status, receiver subvolume presence, read-only state, and expected snapshot metadata. Periodically perform a full restore drill and compare application-level invariants, not only filenames.

Useful evidence includes btrfs subvolume list, btrfs property get, filesystem space and device status, sender/receiver logs, and restore-test results. If an incremental receive fails, keep the parent unchanged and investigate the exact error. Re-baseline with a new full send only after deciding how to preserve existing history and prevent a partial receive from being mistaken for a valid snapshot.

Define measurable acceptance: every snapshot has a recorded source and parent; transfers advance the baseline only after verified receiver success; failed or interrupted transfers do not silently discard the last good replica; retention leaves enough room for a full recovery window; and a restore drill validates the application. That process turns send/receive from a shell pipeline into a defensible replication system.

Related:

Sources:

Comments