Send Btrfs Snapshots with btrfs send, Full or Incremental
Use btrfs send to turn a read-only snapshot into a stream you can save or pipe to another Btrfs filesystem. You can send the whole snapshot, or only what changed since an earlier one. Allow 10 to 30 minutes for a first test, plus however long the data takes to move.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the command and choose the snapshots
- 2. Make a full stream for the first transfer
- 3. Receive the full stream on the destination
- 4. Send only the changes from a parent snapshot
- 5. Use clone sources only when both sides match
- 6. Choose protocol and diagnostic options deliberately
- 7. Diagnose failures without harming your source
The stream can go to a file or straight into btrfs receive on the destination. Either way, the source snapshot is only read, never changed.
1. Check the command and choose the snapshots
This guide uses the installed btrfs-progs 6.6.3 command. It assumes the source filesystem is mounted and you already have snapshots at paths such as /btrfs/snapshots/home-2026-09-22.
- The snapshot must be read-only. A read-only mount is not enough: Btrfs send needs the snapshot itself to stay read-only.
- No writable route in. The same subvolume must not be writable through another mount while the send runs.
$ btrfs --version
btrfs-progs v6.6.3
$ btrfs subvolume show /btrfs/snapshots/home-2026-09-22
The second command is an inspection step. Its output is host-specific, but it should identify a Btrfs subvolume. If it fails, stop and correct the path before attempting a send.
Tip
Reading an existing snapshot normally does not need sudo. Use elevated privileges only when your account cannot traverse the mount or read the snapshot.
2. Make a full stream for the first transfer
A full send contains the entire snapshot data and metadata. Give the output a new name so an existing backup cannot be silently replaced. The -f option writes the stream to a file instead of standard output.
$ snapshot=/btrfs/snapshots/home-2026-09-22
$ stream=/var/backups/btrfs/home-2026-09-22.full.stream
$ test ! -e "$stream" || { printf 'refusing to overwrite %s\n' "$stream" >&2; exit 1; }
$ btrfs send -f "$stream" "$snapshot"
$ printf 'send exit status: %s\n' "$?"
send exit status: 0
$ test -s "$stream" && wc -c < "$stream"
1843200
The byte count is illustrative: your stream will differ with the snapshot contents. A successful command returns status 0 and creates a non-empty stream. The stream is binary, so do not inspect it in a text editor.
Checkpoint
You now have a complete stream that can be copied to the receiving host. Keep the source snapshot unchanged until the stream has been received and checked; do not delete or alter it merely because the file exists.
3. Receive the full stream on the destination
Copy the stream to a directory on the destination Btrfs filesystem, then pass it to btrfs receive. The destination argument is a mounted directory, not a device path. Receiving creates the sent subvolume, so pick a directory where that name is expected and where you have enough free space.
destination=/srv/btrfs-receive
test -d "$destination" && mountpoint -q "$destination"
btrfs receive "$destination" < /var/backups/btrfs/home-2026-09-22.full.stream
printf 'receive exit status: %s\n' "$?"
receive exit status: 0
The mountpoint check stops you feeding a stream into an ordinary directory on the wrong filesystem.
Warning
Receiving changes the destination filesystem, so do not run it casually in a production path. Confirm the destination and the stream name before pressing Enter.
Recovery
If receive fails, keep the stream and read the error before retrying. Do not delete a partially received subvolume until you have identified it with btrfs subvolume list "$destination" and confirmed that removal is safe.
4. Send only the changes from a parent snapshot
Once the first snapshot has been received, an incremental send can use it as the parent. Three conditions:
- Both snapshots are read-only. That means the parent and the new one.
- The parent exists on both sides. It must be present on sender and receiver, in the corresponding state.
$ parent=/btrfs/snapshots/home-2026-09-22
$ current=/btrfs/snapshots/home-2026-09-23
$ stream=/var/backups/btrfs/home-2026-09-23.incremental.stream
$ test ! -e "$stream" || { printf 'refusing to overwrite %s\n' "$stream" >&2; exit 1; }
$ btrfs send -p "$parent" -f "$stream" "$current"
$ printf 'send exit status: %s\n' "$?"
send exit status: 0
$ test -s "$stream" && wc -c < "$stream"
245760
This stream describes the changes from parent to current. It is not a self-contained replacement for the full stream. Receive it on the destination where the matching parent snapshot already exists. If the parent is missing, was changed, or is not the same snapshot state, the incremental workflow cannot rebuild the intended result.
Tip
Record the parent used for every incremental stream. Putting a date such as home-2026-09-22 in both the snapshot and stream names makes recovery much easier.
5. Use clone sources only when both sides match
The -c option supplies an additional snapshot that Btrfs can use as a clone source. It can shrink the data an incremental stream carries, and you can repeat it. It is also a sharp edge: every clone source must be in exactly the same state on sender and receiver.
$ btrfs send -p "$parent" -c /btrfs/snapshots/home-2026-09-15 \
-f "$stream" "$current"
Warning
Do not add -c because a similarly named snapshot happens to exist on the destination. A changed read-only status or changed contents can invalidate the assumption. If you cannot prove the destination clone source is the same received snapshot, omit -c and use only the verified parent.
6. Choose protocol and diagnostic options deliberately
The default send protocol is version 1. Protocol 2 encodes file data more efficiently and is required for --compressed-data. It needs at least btrfs-progs 6.0 on both ends and Linux 6.0 on the sender. Passing --proto 0 asks for the highest version the running kernel supports, so use it only when you know the receiver can cope.
$ btrfs send --proto 2 -f "$stream" "$current"
$ btrfs send --compressed-data -f "$stream" "$current"
$ btrfs send --no-data "$current" > metadata-only.stream
--compressed-datacan keep filesystem-compressed data as it is in the stream, but still needs protocol 2 or higher.--no-datadeliberately omits file data. It cannot transfer the snapshot; it is for inspecting metadata differences, not for a restorable backup.-vgives readable generated-command diagnostics.-qsuppresses non-error messages, which suits scripts.
Warning
A successful metadata-only command is not a usable backup. Also remember the stream itself still needs to be saved or piped; diagnostic output is no substitute for it.
7. Diagnose failures without harming your source
A non-zero status means the send failed. Work through these checks:
- Paths and permissions. Check the snapshot paths and read permissions.
- Space. Check free space for a file output.
- Read-only flags. Confirm every involved snapshot is read-only.
- Parent. For an incremental send, verify the parent is the intended earlier snapshot.
- Clone sources. Verify exact identity on both sides rather than retrying with more options.
$ test -r "$parent" && echo parent-readable
parent-readable
$ test -r "$current" && echo current-readable
current-readable
$ btrfs subvolume show "$parent" | sed -n '1,12p'
$ btrfs subvolume show "$current" | sed -n '1,12p'
Warning
Do not change snapshot flags while a send operation uses them.
Recovery
If an output file is incomplete, move it aside for investigation rather than passing it off as a backup. A failed receive does not make the stream trustworthy; keep the original stream and investigate the destination state before attempting cleanup.
Done means
- Checked the inputs. The installed version and exact source snapshot paths were verified.
- Snapshots are read-only. Every snapshot in the send stays unchanged during the operation.
- Full before incremental. A full stream was created before any incremental stream was attempted.
- Parents are recorded. Incremental parents and clone sources match the destination state.
- Receive worked. The stream is non-empty, receive returned status 0, and the received subvolume was checked.
- Options understood. Protocol 2, compressed data and metadata-only mode were used only when their compatibility and limits were clear.