BTRFS-BACKUP-NG(1)

πŸ“¦
version 0.9.20 Β· 2 versions
, in fedora 43, fedora 44

πŸ’– NAME πŸ’–

okay so welcome to the manual page for btrfs-backup-ng, bestie β€” it lives in the User Commands part of the manual (that's section 1, iykyk). And what does it do? Automated btrfs backup management with incremental transfers. That's the whole vibe. ✨


🌸 SYNOPSIS 🌸

btrfs-backup-ng [OPTIONS] COMMAND [COMMAND_OPTIONS]

literally that's it. options first, then a command, then that command's own options. 🫢


πŸ’œ DESCRIPTION πŸ’œ

btrfs-backup-ng is a modern, feature-rich tool for automating BTRFS snapshot backup management. She's not doing one thing, she's doing the whole wardrobe:

  • TOML configuration files πŸ’…
  • time-based retention policies
  • parallel execution
  • stream compression
  • space-aware operations with quota support
  • automatic subvolume detection
  • robust SSH transfer support

Main character energy, fr fr. πŸ‘‘

πŸ¦‹ The two modes πŸ¦‹

The tool can run in two modes, and you gotta know which one you're in:

  • Config-driven mode β€” you use a TOML configuration file to define volumes, targets, and retention policies. You get here through subcommands like run, snapshot, transfer. Very organized, very demure.
  • Legacy mode β€” you give source and destination paths straight on the command line, just like the original btrfs-backup tool did. The source, the destination and an absolute -f folder all have to exist; see PATHS below for the whole situation. Snapshots get taken into .snapshots inside the source, or into the -f folder. And here's the important bit: when that folder is empty while .snapshots is still holding the chain from an earlier release, the run is refused rather than started as a brand-new chain β€” unless you pass --accept-full-send. Don't ignore that one, bestie, it's protecting you. 😭

✨ GLOBAL OPTIONS ✨

  • -h, --help β€” show the help message and dip. πŸ‘‹
  • -v, --verbose β€” turn on verbose output. more words, more lore.
  • -q, --quiet β€” suppress the non-essential output. silence is golden. 🀫
  • --debug β€” enable debug output with detailed logging.
  • --btrfs-debug β€” runs every btrfs send and btrfs receive with -vv, and logs each line they print, one per file operation, as it arrives, prefixed with the process that printed it. This implies --debug, since those lines get logged at that level. Same setting is available as btrfs_debug = true under [global] in the configuration file. In legacy mode this option has always existed as --btrfs-debug; until this release its output was just... discarded. πŸ—‘οΈπŸ’€ not anymore tho. she's loud now.
  • -V, --version β€” show the version and exit.
  • -c, --config FILE β€” path to the configuration file. Defaults to ~/.config/btrfs-backup-ng/config.toml or /etc/btrfs-backup-ng/config.toml.

πŸŽ€ COMMANDS πŸŽ€


🌈 CONFIGURATION 🌈

Config files use TOML format, and they're searched in this exact order:

  1. CLI specified: --config /path/to/config.toml
  2. User config: ~/.config/btrfs-backup-ng/config.toml
  3. System config: /etc/btrfs-backup-ng/config.toml

Example configuration β€” look at her, she's perfect:

[global]
snapshot_dir = ".snapshots"
incremental = true

[global.retention]
min = "1d"
hourly = 24
daily = 7
weekly = 4
monthly = 12

[[volumes]]
path = "/home"
snapshot_prefix = "home-"

[[volumes.targets]]
path = "/mnt/backup/home"

[[volumes.targets]]
path = "ssh://backup@server:/backups/home"
ssh_sudo = true
compress = "zstd"

🧚 PATHS 🧚

okay this is the part where the rules are rules. Which paths get created is decided by what a path is, not by where it was typed β€” so there are no exceptions. periodt.

  • A backup location must exist and is never created. A source subvolume, a target on every transport (local, raw://, ssh://, raw+ssh://) and an absolute snapshot base are all statements that something is there β€” whether they came from the configuration file, from snapper backup TARGET, or from legacy mode's SOURCE, DESTINATION and absolute -f folder. If one is missing, it's refused with a message naming the likely cause and the remedy; nothing gets created. This is 100% deliberate, and here's why: a target on a removable or network filesystem that wasn't mounted used to have its mount point rebuilt on the root filesystem and the backup written there. And an absolute snapshot_dir that wasn't mounted had the snapshots written to the root filesystem β€” where they succeed. 😭 so. Mount the filesystem, correct the path, or create the directory once by hand, and run again.
  • An output location is created. That's anywhere a command was asked to put something new: the restore destination, verify --temp-dir, and the file written by config init -o, completions install or manpages install.
  • The program's own state is created. Its state directory, transfer cache, run lock, the parents of log_file and transaction_log, and ssh control sockets. Hers. πŸ’…
  • Below a location that exists, one level at a time, never the location itself. The .btrfs-backup-ng tree under a target, a relative snapshot_dir under its volume, the per-volume directory under an absolute snapshot_dir, the .snapshots slots of a snapper backup, and the lock directory on a remote target.

πŸ’œ RETENTION POLICIES πŸ’œ

Time-based retention (applied by the btrfs-backup-ng-prune(1) command) uses these settings. For each bucket, the oldest snapshot in the interval is kept β€” that's the first-of-interval representative, matching the btrbk/snapper convention. πŸŽ€

  • min β€” minimum retention period (e.g., "1d", "12h", "1M", "1y"). Nothing is deleted before this age. Month and year units are calendar-aware: 1M is one calendar month (not a flat 30 days) and 1y is one calendar year. The value all keeps every snapshot in the scope for ever, whatever the bucket counts say (that's btrbk's snapshot_preserve_min all, its default). An invalid value fails loudly and prunes nothing.
  • hourly β€” number of hourly snapshots to keep (one per clock hour).
  • daily β€” number of daily snapshots to keep (one per day).
  • weekly β€” number of weekly snapshots to keep (one per ISO-8601 week).
  • monthly β€” number of monthly snapshots to keep (one per calendar month).
  • yearly β€” number of yearly snapshots to keep (one per year).

🌸 COMPRESSION 🌸

Supported compression methods for transfers:

  • none β€” no compression. that's the default. πŸ™ˆ
  • zstd β€” Zstandard compression. recommended, fast with a good ratio. the girlies love her. πŸ”₯
  • gzip β€” standard gzip compression.
  • lz4 β€” very fast compression with a lower ratio.
  • pigz β€” parallel gzip (multi-threaded). she multitasks.
  • lzop β€” fast compression, low CPU usage.

πŸ¦‹ SSH TARGETS πŸ¦‹

Remote targets use SSH URLs, like this:

ssh://user@host:/path/to/backup

And here are the options for SSH targets:

  • ssh_sudo β€” use sudo for btrfs commands on the remote host.
  • ssh_key β€” path to the SSH private key file. πŸ”‘
  • ssh_host_key_policy β€” host-key verification policy: accept-new (default; trust-on-first-use, but rejects a changed key) or strict (only hosts already present in known_hosts; an unknown host is refused). Also settable per run with --ssh-host-key-policy.

πŸ” SSH SECURITY πŸ”

btrfs-backup-ng verifies the remote host's SSH key on every connection. The default policy, accept-new, trusts a host's key on first contact and pins it, then refuses to connect if that key later changes β€” which is the usual man-in-the-middle signal, bestie, do not ignore her. Set ssh_host_key_policy = strict (or pass --ssh-host-key-policy strict) to require that the host already be present in known_hosts and refuse any unknown host.

Under sudo, host keys are verified and pinned against the invoking operator's ~/.ssh/known_hosts (not root's), so the trust you established interactively still applies. Operator ssh_opts are honored too, so an explicit StrictHostKeyChecking in there takes precedence over the tool's default.

SSH ControlMaster sockets are created in an unpredictable, private (0700) directory β€” preferring $XDG_RUNTIME_DIR β€” and they're removed when the connection closes, so a local user can't hijack the multiplexed connection. The internal command lock is likewise placed in a private per-user directory instead of a predictable /tmp path. And it is left in place after a run, deliberately: it's an flock, which excludes by inode, and removing the file while another process is waiting on it would let two processes each lock a different file at the same path. so yeah, she stays. πŸ’…

🌈 RAW TARGETS 🌈

So like… normal targets need btrfs receive on the other end, right? Which is great until the other end is literally just an NFS share living its best life. Raw targets are the fix: they write the btrfs send stream straight into files, no btrfs receive involved, periodt. That means you can back up onto non-btrfs filesystems β€” NFS, SMB, cloud storage, whatever β€” and optionally squeeze in compression and encryption. Iconic behaviour honestly. πŸ’…

URL schemes

  • raw:///path β€” a local raw target. Yes that's three slashes, she's built different, don't touch it.
  • raw+ssh://user@host/path β€” remote raw target, but make it ✨SSH✨.

Configuration options

  • compress β€” which compression method to use: gzip, pigz, zstd, lz4, xz, lzo, bzip2, pbzip2.
  • encrypt β€” which encryption method to use: gpg, openssl_enc.
  • gpg_recipient β€” the GPG key recipient. Required if you're doing gpg encryption, no skipping it bestie.
  • gpg_keyring β€” optional GPG keyring path.
  • openssl_cipher β€” the OpenSSL cipher (default: aes-256-cbc).

Example raw target configuration

[[volumes.targets]]
path = "raw:///mnt/nas/backups/home"
compress = "zstd"
encrypt = "gpg"
gpg_recipient = "backup@example.com"

For OpenSSL encryption, the passphrase comes from an environment variable, so go ahead and export that vibe:

export BTRFS_BACKUP_PASSPHRASE="your_passphrase"
# or (btrbk compatible)
export BTRBK_PASSPHRASE="your_passphrase"

File naming convention

She has a whole naming system and it's actually so satisfying to look at:

snapshot-name.btrfs                    # No compression/encryption
snapshot-name.btrfs.zst                # Compressed with zstd
snapshot-name.btrfs.gpg                # GPG encrypted
snapshot-name.btrfs.zst.gpg            # Compressed + GPG encrypted
snapshot-name.btrfs.enc                # OpenSSL encrypted
snapshot-name.btrfs.zst.enc            # Compressed + OpenSSL encrypted

✨ EXAMPLES ✨

Okay here's the greatest hits, use them, screenshot them, study them πŸŽ€

  • Run all backup jobs:
btrfs-backup-ng run
  • Create snapshots only:
btrfs-backup-ng snapshot
  • Run with compression and rate limiting:
btrfs-backup-ng run --compress zstd --rate-limit 50M
  • List all snapshots:
btrfs-backup-ng list
  • Validate configuration:
btrfs-backup-ng config validate
  • Install hourly timer:
sudo btrfs-backup-ng install --timer hourly
  • Restore from backup:
btrfs-backup-ng restore ssh://server:/backups/home /mnt/restore
  • Estimate backup sizes:
btrfs-backup-ng estimate --volume /home
  • Estimate with space check:
btrfs-backup-ng estimate --volume /home --check-space
  • Detect btrfs subvolumes:
sudo btrfs-backup-ng config detect

πŸ¦‹ FILES πŸ¦‹

Where she keeps her stuff, the whole tour 🫢

  • ~/.config/btrfs-backup-ng/config.toml β€” user configuration file.
  • /etc/btrfs-backup-ng/config.toml β€” system-wide configuration file.
  • /var/log/btrfs-backup-ng.log β€” the default log file location, that's where the drama gets recorded.
  • ~/.local/share/btrfs-backup-ng/transactions.jsonl β€” transaction log for user installations.
  • /var/lib/btrfs-backup-ng/transactions.jsonl β€” transaction log for system installations.

πŸ’€ EXIT STATUS πŸ’€

She tells you how it went, no mind games:

  • 0 β€” success. ate and left no crumbs πŸ’…
  • 1 β€” general error. not the vibe.
  • 2 β€” configuration error. bestie… check your config first, I'm begging 😭

πŸ‘‘ SEE ALSO πŸ‘‘

Go read your siblings, they're all iconic:

btrfs-backup-ng-run(1), btrfs-backup-ng-snapshot(1), btrfs-backup-ng-transfer(1), btrfs-backup-ng-prune(1), btrfs-backup-ng-list(1), btrfs-backup-ng-status(1), btrfs-backup-ng-restore(1), btrfs-backup-ng-verify(1), btrfs-backup-ng-estimate(1), btrfs-backup-ng-config(1), btrfs-backup-ng-install(1), btrfs(8), btrfs-send(8), btrfs-receive(8)


πŸ› BUGS πŸ›

Found a bug? Not her fault (jk, maybe) β€” report it at https://github.com/berrym/btrfs-backup-ng/issues and she'll handle it 🫢


πŸ’– AUTHORS πŸ’–

  • Michael Berry <trismegustis@gmail.com> (2024-present)
  • Robert Schindler <r.schindler@efficiosoft.com> (2017)
  • Chris Lawrence <lawrencc@debian.org> (2014, original author)

Copyright (C) 2014-2026 Chris Lawrence, Robert Schindler, Michael Berry. Licensed under the MIT License.


And that's a wrap on btrfs-backup-ng, bestie ✨ this whole page dropped as btrfs-backup-ng, January 2026, and it lives in section BTRFS-BACKUP-NG(1) β€” go forth, back up your subvolumes, and stay slaying πŸ’œπŸŒΈ

πŸ“¦ where this page comes from