btrfs Snapshots and send/receive Incremental Backups - 夜莺博客

btrfs Snapshots and send/receive Incremental Backups

btrfs snapshots are cheap, atomic and, unlike rsync, they capture a consistent point in time without re-reading the whole tree. Combined with btrfs send and btrfs receive, they give you incremental, verifiable backups that transfer only the difference between two snapshots. The mechanism has a few strict requirements — read-only snapshots and a shared parent snapshot on both ends — and the error messages when those requirements are not met are opaque. This article covers the workflow with the checks that keep it working.

Snapshots in one paragraph

A btrfs snapshot is a subvolume that shares its data extents with the original; creating one is O(1) and it consumes no space until data diverges. Snapshots are not recursive, so each subvolume needs its own snapshot entry. Make backup snapshots read-only — btrfs send refuses to work with writable snapshots, which is a deliberate safeguard.

# Layout: @data is the live subvolume
sudo btrfs subvolume snapshot -r /srv/data /srv/.snapshots/data-2026-09-21
sudo btrfs subvolume list /srv | tail -5

Full send: the seed

sudo btrfs send /srv/.snapshots/data-2026-09-21 |   sudo btrfs receive /mnt/backup/data-dest

This streams the whole snapshot. It is the slow step — plan for a full pass before you switch to a schedule, and make sure the destination filesystem is also btrfs (receive only works onto btrfs).

Incremental send: the delta

sudo btrfs snapshot -r /srv/data /srv/.snapshots/data-2026-09-22

sudo btrfs send -p /srv/.snapshots/data-2026-09-21                  /srv/.snapshots/data-2026-09-22 |   sudo btrfs receive /mnt/backup/data-dest

With -p you name the parent snapshot. Both ends must have that parent: the sender must still hold it locally and the receiver must already have received it. If either is missing, receive fails with "cannot find parent subvolume" and you must either re-seed or reuse an older parent that exists in both places. Because of that constraint, a retention policy that deletes local snapshots too aggressively will break the backup chain.

Over SSH to a remote host

sudo btrfs send -p /srv/.snapshots/data-2026-09-21 /srv/.snapshots/data-2026-09-22 |   ssh backup@10.10.10.9 "sudo btrfs receive /srv/backup/dest"

Two operational details: run the send as the user that owns the snapshots (or root), and keep the destination path stable so the parent lookup works. For multi-hour transfers, wrap the pipeline in a script that records the parent used for each transfer — when the chain breaks, that log tells you exactly which parent to re-establish.

Practical wrapper

#!/bin/bash
set -euo pipefail
https://btrfs.readthedocs.io/en/latest/btrfs-send.html=/srv/data
SNAPDIR=/srv/.snapshots
DEST=/mnt/backup/data-dest
TODAY=$SNAPDIR/data-$(date +%F)
PREV=$(ls -1d $SNAPDIR/data-20* 2>/dev/null | tail -1 || true)

[ -d "$TODAY" ] || btrfs subvolume snapshot -r "$https://btrfs.readthedocs.io/en/latest/btrfs-send.html" "$TODAY"

if [ -z "$PREV" ]; then
  btrfs send "$TODAY" | btrfs receive "$DEST"
else
  btrfs send -p "$PREV" "$TODAY" | btrfs receive "$DEST"
fi

# retention: never delete a snapshot that is the parent of the last transfer
ls -1d $SNAPDIR/data-20* | head -n -7 | xargs -r -n1 btrfs subvolume delete

Restoring

# Inspect what exists at the destination
sudo btrfs subvolume list /mnt/backup/data-dest

# Restore a single file, fastest path
sudo cp -a /mnt/backup/data-dest/data-2026-09-21/some/file.conf /etc/restored.conf

# Restore a full subvolume
sudo btrfs subvolume snapshot /mnt/backup/data-dest/data-2026-09-21 /srv/data-restored

Because each received snapshot is a normal read-only subvolume, restoring is a copy or a snapshot operation rather than a proprietary restore tool. That is the main advantage over data protection from a storage appliance — and the main disadvantage is that you have to verify the chain yourself.

Checks and pitfalls

  • Writable snapshotssend fails; always create with -r.
  • Missing parent at the receiver — the classic chain-break; retain parents on both ends.
  • Non-btrfs destinationreceive requires btrfs; use pipes to files if you need to stage.
  • Snapshotting the wrong subvolume — snapshots are not recursive, so nested subvolumes are not included; snapshot each one explicitly.
  • Ignoring space — snapshots grow with divergence, so monitor free space on both source and destination.
  • No restore test — a receive that succeeded does not prove the data is readable; verify a file from an older snapshot monthly.

Choosing between btrfs send/receive and rsync

Criterion btrfs send/receive rsync + hard links
Consistency Point-in-time, atomic Files read over time; risk of torn backups
Speed after seeding Only changed extents Only changed files, but full metadata scan
Destination requirement btrfs Any filesystem
Chain fragility Parent snapshots must exist on both ends Each snapshot independent

Compression, encryption and bandwidth

Three practical options change how a send/receive pipeline behaves on a real network:

# Compress the stream (helps for text, hurts for already-compressed data)
sudo btrfs send -p $PREV $TODAY | gzip -c | ssh backup@10.10.10.9 "gzip -d | sudo btrfs receive /srv/backup/dest"

# Encrypt in transit without relying on the transport
sudo btrfs send -p $PREV $TODAY | age -r $RECIPIENT | ssh backup@10.10.10.9 "age -d -i key.txt | sudo btrfs receive /srv/backup/dest"

# Limit throughput so the backup does not saturate the link
sudo btrfs send -p $PREV $TODAY | pv -qL 50m | ssh backup@10.10.10.9 "sudo btrfs receive /srv/backup/dest"

Note that compressing the stream also disables the small efficiency trick of letting btrfs's own encoding do the work; measure before enabling it. For long-distance replication, encryption at the tool level protects against a compromised transport hop, while SSH alone protects only the connection.

Verifying a send/receive chain

A chain that has silently broken will only reveal itself when you need a restore. Check it deliberately:

  1. List received snapshots on the destination and confirm the count matches your expectations for the retention window.
  2. Compare the newest received snapshot's creation time with the schedule; a gap means a run failed.
  3. Mount or snapshot the newest snapshot read-only and compare a checksum of a few files against production.
  4. Confirm the parent snapshot used by the last transfer still exists on both sides — if it was pruned locally, the next incremental will fail.
  5. Record the result; a verification log with dates is what tells you when the chain broke if it breaks later.
# Quick integrity spot check
sudo mount -o ro,subvol=/data-2026-09-21 /dev/sdb1 /mnt/verify
sha256sum /mnt/verify/some/critical/file
sudo umount /mnt/verify

When btrfs is not the right answer

btrfs send/receive is excellent for a same-filesystem replication pair and for keeping many cheap snapshots on one host. It is a poor fit for backing up to object storage, for cross-platform restores, and for environments where the operational team is not comfortable with subvolume semantics. In those cases, the file-based rsync rotation covered in the related articles below is more portable and easier to hand to a generalist team.

Related articles

File-based backup with the same rotation model is covered in the rsync guide, and the ZFS equivalent (snapshots plus send/receive) is in ZFS zpool, raidz and vdev administration. If a full backup filesystem is the symptom, Docker overlay2 disk full cleanup is a frequent culprit, and iSCSI-attached storage paths are covered in Linux multipath configuration.

原文链接:https://btrfs.readthedocs.io/en/latest/btrfs-send.html