Complete reference for all btrfs-backup-ng commands and options.
These options can be used with any command:
-h, --help Show help message and exit
-V, --version Show version and exit
-c, --config FILE Path to configuration file
-v, --verbose Show debug output on the console
-q, --quiet Suppress non-essential output
--debug Enable debug output
--btrfs-debug Run btrfs send and receive with -vv and log every line they
print, one per file operation, as it arrives; implies
--debug. Same as [global] btrfs_debug = true
Two kinds of command, two places to put SSH settings:
| command shape | examples | SSH options come from |
|---|---|---|
| names a target on the command line | restore, verify, estimate, snapper backup, snapper restore, raw list, raw verify |
--ssh-sudo, --ssh-key, --ssh-auth-sock, --ssh-host-key-policy |
| runs against the configuration | run, snapshot, transfer, prune |
the target's own keys in the config file |
This is deliberate, not an omission. A config-driven command can fan out to several
targets in one invocation -- an offsite server and a local NAS, with different
users and different keys -- so a single --ssh-key on the command line would have
no unambiguous meaning. Those commands therefore read ssh_sudo, ssh_key,
ssh_auth_sock and ssh_host_key_policy from each [[volumes.targets]] entry,
where they can differ per target:
[[volumes.targets]]
path = "ssh://backup@offsite:/mnt/backups"
ssh_key = "/home/user/.ssh/id_ed25519_offsite"
ssh_sudo = true
[[volumes.targets]]
path = "raw+ssh://archive@nas:/srv/archive"
ssh_key = "/home/user/.ssh/id_ed25519_nas"Where a command names one target, the flag is unambiguous and is accepted.
Note that --ssh-sudo does NOT mean the same thing on every scheme: on ssh://
it elevates only btrfs, while on raw+ssh:// it elevates every remote command.
See the Permissions section of the README for what each scheme needs.
Execute all configured backup jobs (snapshot + transfer + prune).
btrfs-backup-ng run [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be done without making changes |
--parallel-volumes N |
Max concurrent volume backups (overrides config) |
--parallel-targets N |
Max concurrent target transfers per volume |
--compress METHOD |
Compression method: none, gzip, zstd, lz4, pigz, lzop |
--rate-limit RATE |
Bandwidth limit (e.g., '10M', '1G', '500K') |
--progress |
Force progress bar display |
--no-progress |
Disable progress bar display |
Examples:
# Full backup run
btrfs-backup-ng run
# Dry run to see what would happen
btrfs-backup-ng run --dry-run
# With compression and rate limiting
btrfs-backup-ng run --compress=zstd --rate-limit=50M
# Parallel execution
btrfs-backup-ng run --parallel-volumes=3 --parallel-targets=2Create snapshots only, without transferring to targets.
btrfs-backup-ng snapshot [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be done without making changes |
--volume PATH |
Only snapshot specific volume(s), can be repeated |
Examples:
# Snapshot all configured volumes
btrfs-backup-ng snapshot
# Snapshot specific volume only
btrfs-backup-ng snapshot --volume=/home
# Dry run
btrfs-backup-ng snapshot --dry-runTransfer existing snapshots to targets without creating new snapshots.
btrfs-backup-ng transfer [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be done without making changes |
--volume PATH |
Only transfer specific volume(s) |
--compress METHOD |
Compression method (overrides config) |
--rate-limit RATE |
Bandwidth limit (overrides config) |
Examples:
# Transfer all pending snapshots
btrfs-backup-ng transfer
# Transfer with compression
btrfs-backup-ng transfer --compress=zstd
# Transfer specific volume with rate limiting
btrfs-backup-ng transfer --volume=/home --rate-limit=10MApply retention policies to clean up old snapshots and backups.
btrfs-backup-ng prune [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be deleted without making changes |
--yes, -y |
Skip the interactive confirmation prompt (for automation) |
--force |
Prune even under a degenerate policy that would keep only the latest snapshot |
Confirmation prompt. On an interactive terminal, a real (non---dry-run) prune first prints
the full deletion plan and asks for confirmation before deleting anything:
About to delete 5 snapshot(s)/backup(s):
source /home -- 2:
- home-20260101-000000
- home-20260102-000000
target /mnt/backup/home -- 3:
- home-20260103-000000
- home-20260104-000000
- home-20260105-000000
Proceed with deleting 5? [y/N]
Declining aborts with zero deletions. In a non-interactive context (cron, a pipe, a script)
the prompt is skipped and the prune proceeds — use --yes to skip the prompt in an interactive
terminal too.
Degenerate-policy guard. A retention policy that would keep only the latest snapshot — all
time buckets set to 0 and min ≤ 1 day — is almost always a misconfiguration that would
delete your entire history. Prune refuses such a policy (deleting nothing, with a non-zero
exit) unless you pass --force, and this refusal applies even non-interactively so a
fat-fingered config cannot silently wipe your backups. A legitimate short-window policy (e.g.
min = "30d" with all buckets 0) is not degenerate and prunes normally.
Examples:
# Apply retention policies (prompts for confirmation on an interactive terminal)
btrfs-backup-ng prune
# See what would be deleted, change nothing
btrfs-backup-ng prune --dry-run
# Non-interactive / automation: skip the prompt
btrfs-backup-ng prune --yes
# Intentionally prune under a keep-only-latest policy
btrfs-backup-ng prune --forceShow snapshots and backups across all configured volumes and targets.
btrfs-backup-ng list [OPTIONS]Options:
| Option | Description |
|---|---|
--volume PATH |
Only list specific volume(s) |
--json |
Output in JSON format |
Examples:
# List all snapshots
btrfs-backup-ng list
# List specific volume
btrfs-backup-ng list --volume=/home
# JSON output for scripting
btrfs-backup-ng list --jsonShow job status, last run times, statistics, and transaction history.
btrfs-backup-ng status [OPTIONS]Options:
| Option | Description |
|---|---|
-t, --transactions |
Show recent transaction history |
-n, --limit N |
Number of transactions to show (default: 10) |
Examples:
# Basic status
btrfs-backup-ng status
# Status with transaction history
btrfs-backup-ng status --transactions
# Show last 20 transactions
btrfs-backup-ng status -t -n 20Output includes:
- Volume and target health status
- Snapshot counts
- Transaction statistics (if transaction_log configured)
- Recent transaction history (with --transactions flag)
Configuration management subcommands.
Validate the configuration file.
btrfs-backup-ng config validateExamples:
# Validate default config location
btrfs-backup-ng config validate
# Validate specific config file
btrfs-backup-ng -c /path/to/config.toml config validateBesides the file itself, config validate checks, on this machine and
without root, that each enabled volume's source can be backed up: it can be
read from this account, exists, and is a directory on btrfs. A native source
must also be a subvolume root -- btrfs subvolume snapshot refuses anything
else -- and a directory inside a subvolume is reported with the subvolume that
holds it. A snapper source (source = "snapper") is not required to be one:
snapper takes its snapshots, and with config_name = "auto" any path at or
below a snapper subvolume selects that config. Remote targets are not probed;
doctor does that. doctor asks the same question of each source.
Exit status:
0-- the file is valid and its volumes are usable here1-- no config file was found, or the file itself is invalid and must be edited2-- the file is valid, but this machine cannot back up these volumes: a source is missing, cannot be read from this account, is not a directory, is not on btrfs, or (native sources) is not a subvolume root
Note — unknown keys are reported. Loading the config warns about any key a parser does not recognize, e.g.
Unknown config key 'ssh_prot' in [volumes[0].targets[0]] (ignored). A misspelled or misplaced key (a typo, or a value under the wrong table) is silently ignored by the loader, which would quietly void a safety setting such asretention,encrypt, orssh_host_key_policy— the warning surfaces it so you can fix it. The warning is non-fatal; the rest of the config still loads. Any command that loads the config prints these warnings;config validateis the quickest way to check.
Generate an example configuration file.
btrfs-backup-ng config init [OPTIONS]Options:
| Option | Description |
|---|---|
-o, --output FILE |
Output file (default: stdout) |
Examples:
# Print example config to stdout
btrfs-backup-ng config init
# Save to file
btrfs-backup-ng config init -o ~/.config/btrfs-backup-ng/config.tomlImport a btrbk configuration file and convert to TOML.
btrfs-backup-ng config import FILE [OPTIONS]Arguments:
| Argument | Description |
|---|---|
FILE |
Path to btrbk.conf file |
Options:
| Option | Description |
|---|---|
-o, --output FILE |
Output file (default: stdout) |
Examples:
# Convert and print to stdout
btrfs-backup-ng config import /etc/btrbk/btrbk.conf
# Convert and save to file
btrfs-backup-ng config import /etc/btrbk/btrbk.conf -o config.tomlScan the system for btrfs subvolumes and suggest backup configurations.
btrfs-backup-ng config detect [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format for scripting |
-w, --wizard |
Launch interactive wizard with detected volumes |
Examples:
# Scan for subvolumes (requires root for full access)
sudo btrfs-backup-ng config detect
# Output in JSON format
sudo btrfs-backup-ng config detect --json
# Launch wizard with detected volumes
sudo btrfs-backup-ng config detect --wizardThe detect command categorizes subvolumes as:
- Recommended: User data like
/homethat should be backed up - Optional: System data (
/opt,/var/log) that may or may not need backup - Excluded: Existing snapshots and system-internal subvolumes
Install systemd timer and service for automated backups.
btrfs-backup-ng install [OPTIONS]Options:
| Option | Description |
|---|---|
--timer PRESET |
Use preset interval: hourly, daily, weekly |
--oncalendar SPEC |
Custom OnCalendar specification |
--user |
Install as user service (no root required) |
Timer Presets:
| Preset | OnCalendar Value | Description |
|---|---|---|
hourly |
*:00 |
Every hour at minute 00 |
daily |
*-*-* 02:00:00 |
Every day at 2:00 AM |
weekly |
Sun *-*-* 02:00:00 |
Every Sunday at 2:00 AM |
Installation Modes:
| Mode | Location | Requires |
|---|---|---|
| System-wide (default) | /etc/systemd/system/ |
root (sudo) |
User-level (--user) |
~/.config/systemd/user/ |
None |
Examples:
# System-wide installation (requires root)
sudo btrfs-backup-ng install --timer=hourly
sudo btrfs-backup-ng install --timer=daily
sudo btrfs-backup-ng install --timer=weekly
# Custom schedules
sudo btrfs-backup-ng install --oncalendar='*:0/15' # Every 15 minutes
sudo btrfs-backup-ng install --oncalendar='*:0/5' # Every 5 minutes
sudo btrfs-backup-ng install --oncalendar='*-*-* 02:00:00' # Daily at 2am
sudo btrfs-backup-ng install --oncalendar='Mon *-*-* 03:00' # Monday at 3am
# User-level installation (no root required)
btrfs-backup-ng install --user --timer=hourlyAfter installation, enable the timer:
# System-wide
sudo systemctl daemon-reload
sudo systemctl enable --now btrfs-backup-ng.timer
# User-level
systemctl --user daemon-reload
systemctl --user enable --now btrfs-backup-ng.timerVerify timer is active:
systemctl list-timers btrfs-backup-ng.timer # System-wide
systemctl --user list-timers btrfs-backup-ng.timer # User-levelRemove installed systemd timer and service files.
btrfs-backup-ng uninstallExamples:
# Remove system-wide installation
sudo btrfs-backup-ng uninstall
# Remove user-level installation
btrfs-backup-ng uninstallEstimate backup transfer sizes and optionally check destination space.
btrfs-backup-ng estimate [OPTIONS] SOURCE DESTINATION
btrfs-backup-ng estimate --volume PATH [--target INDEX] [--check-space]Options:
| Option | Description |
|---|---|
--volume PATH |
Estimate for volume defined in config |
--target INDEX |
Target index to estimate for (0-based) |
--prefix PREFIX |
Snapshot prefix filter |
--check-space |
Check if destination has sufficient space |
--safety-margin PERCENT |
Safety margin percentage (default: 10%) |
--ssh-sudo |
Use sudo on remote host |
--ssh-key FILE |
SSH private key file |
--ssh-auth-sock PATH |
Explicit ssh-agent socket (overrides auto-discovery) |
--fs-checks MODE |
Filesystem check mode: auto, strict, skip |
--no-fs-checks |
Skip filesystem checks (alias for --fs-checks=skip) |
--json |
Output in JSON format |
Examples:
# Basic estimate
btrfs-backup-ng estimate /mnt/snapshots /mnt/backup
# Estimate for configured volume
btrfs-backup-ng estimate --volume /home
# Estimate with destination space check
btrfs-backup-ng estimate --volume /home --check-space
# Space check with custom safety margin
btrfs-backup-ng estimate /mnt/snapshots /mnt/backup --check-space --safety-margin 20
# JSON output for scripting
btrfs-backup-ng estimate --volume /home --check-space --jsonSpace Check Output:
When --check-space is enabled, the output includes destination space information:
- Filesystem free space
- Quota limits (if btrfs quotas are enabled)
- Effective available space (the more restrictive of fs or quota)
- Required space with safety margin
- Status (OK or INSUFFICIENT)
Diagnose backup system health and optionally fix common issues.
btrfs-backup-ng doctor [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format for scripting |
--check CATEGORY |
Check specific category: config, snapshots, transfers, system |
--fix |
Auto-fix safe issues (stale locks, temp files) |
--interactive |
Confirm each fix before applying (requires --fix) |
-q, --quiet |
Only show problems (suppress OK messages) |
--volume PATH |
Check specific volume only |
Categories:
| Category | What It Checks |
|---|---|
config |
Config file exists and valid, volume sources usable (the same check as config validate), targets reachable, compression programs available |
snapshots |
Orphaned snapshots, missing snapshots, broken parent chains |
transfers |
Stale locks (dead processes), incomplete transfers, recent failures in transaction log |
system |
Destination space, quota limits, systemd timer status, backup age |
Exit Codes:
| Code | Meaning |
|---|---|
| 0 | Healthy (all OK or INFO findings) |
| 1 | Warnings present |
| 2 | Errors or critical issues |
Examples:
# Full diagnostics
btrfs-backup-ng doctor
# JSON output for monitoring integration
btrfs-backup-ng doctor --json
# Check only configuration
btrfs-backup-ng doctor --check config
# Check only snapshot health
btrfs-backup-ng doctor --check snapshots
# Auto-fix safe issues
btrfs-backup-ng doctor --fix
# Interactive fix (confirm each)
btrfs-backup-ng doctor --fix --interactive
# Only show problems
btrfs-backup-ng doctor --quiet
# Check specific volume
btrfs-backup-ng doctor --volume /homeExample Output:
btrfs-backup-ng Doctor
=======================================================
Configuration
[OK] Config file valid
[OK] Volume /home exists and is btrfs subvolume
[WARN] Compression program 'pigz' not found
Snapshots
[OK] Volume /home: 24 snapshots, chain intact
Transfers
[WARN] Stale lock: home-20260110 (process 12345 not running)
[FIXABLE] Run with --fix to remove
System
[OK] Destination: 524 GiB available (52%)
[OK] Systemd timer active, next: 2h 15m
=======================================================
Summary: 7 passed, 2 warnings, 0 errors
Fixable: 1 issue (run with --fix)
JSON Output:
{
"timestamp": "2026-01-06T10:30:00Z",
"duration_seconds": 2.5,
"summary": {"ok": 7, "warnings": 2, "errors": 0, "fixable": 1},
"findings": [
{
"category": "transfers",
"severity": "warn",
"check": "stale_locks",
"message": "Stale lock detected",
"details": {"snapshot": "home-20260110", "pid": 12345},
"fixable": true,
"fix_description": "Remove stale lock file"
}
]
}Note: The chunked transfer feature is experimental. It provides resumable transfers for large snapshots but has not been extensively tested in production environments.
Manage chunked and resumable transfers. This command allows you to list, inspect, resume, pause, and clean up incomplete transfers.
btrfs-backup-ng transfers [SUBCOMMAND] [OPTIONS]List all incomplete transfer sessions.
btrfs-backup-ng transfers list [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format |
--all |
Include completed and failed transfers |
Example Output:
Incomplete Transfers
======================================================================
ID Status Snapshot Progress Age
----------------------------------------------------------------------
abc123 paused home-20260108 5/10 (50%) 2h ago
def456 failed root-20260109 3/8 (37%) 1d ago
----------------------------------------------------------------------
Total: 2 incomplete transfer(s)
Show detailed information about a specific transfer.
btrfs-backup-ng transfers show TRANSFER_ID [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format |
Example:
btrfs-backup-ng transfers show abc123Resume a paused or failed transfer.
btrfs-backup-ng transfers resume TRANSFER_ID [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be done without resuming |
--rate-limit RATE |
Bandwidth limit (e.g., '10M', '1G') |
--progress |
Show progress bars |
--no-progress |
Disable progress bars |
Example:
btrfs-backup-ng transfers resume abc123Pause an active transfer.
btrfs-backup-ng transfers pause TRANSFER_IDClean up stale, completed, or failed transfers.
btrfs-backup-ng transfers cleanup [OPTIONS]Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be cleaned without making changes |
--force |
Remove even active transfers |
--older-than DURATION |
Only cleanup transfers older than duration (e.g., '7d', '24h') |
TRANSFER_ID |
Clean up a specific transfer |
Examples:
# See what would be cleaned
btrfs-backup-ng transfers cleanup --dry-run
# Clean up stale transfers
btrfs-backup-ng transfers cleanup
# Clean up a specific transfer
btrfs-backup-ng transfers cleanup abc123
# Force cleanup of all transfers older than 7 days
btrfs-backup-ng transfers cleanup --older-than 7d --forceList backup operations and their status.
btrfs-backup-ng transfers operations [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format |
--all |
Include archived operations |
Manage snapper-managed snapshots. This command provides integration with snapper for discovering, backing up, and restoring snapper snapshots.
btrfs-backup-ng snapper SUBCOMMAND [OPTIONS]Detect snapper configurations on the system.
btrfs-backup-ng snapper detect [OPTIONS]Options:
| Option | Description |
|---|---|
--json |
Output in JSON format |
Example Output:
Found 2 snapper configuration(s):
root:
Subvolume: /
Snapshots dir: /.snapshots
Status: OK
home:
Subvolume: /home
Snapshots dir: /home/.snapshots
Status: OK
List snapshots for snapper configurations.
btrfs-backup-ng snapper list [OPTIONS]Options:
| Option | Description |
|---|---|
--config NAME |
List snapshots for specific config only |
--type TYPE |
Filter by snapshot type (single, pre, post) |
--json |
Output in JSON format |
Examples:
# List all snapshots
btrfs-backup-ng snapper list
# List only 'root' config snapshots
btrfs-backup-ng snapper list root
# List only 'single' type snapshots
btrfs-backup-ng snapper list --type singleBackup snapper snapshots to a destination.
btrfs-backup-ng snapper backup CONFIG TARGET [OPTIONS]Arguments:
| Argument | Description |
|---|---|
CONFIG |
Snapper configuration name (e.g., 'root', 'home') |
TARGET |
Destination: a local path or ssh://user@host:/path for btrfs destinations, or a raw target (raw:///path, raw+ssh://user@host/path) for non-btrfs destinations. See Raw Targets. The target must exist; the .snapshots tree is created below it. |
Options:
| Option | Description |
|---|---|
--dry-run |
Show what would be done without making changes |
--snapshot NUM |
Backup specific snapshot by number |
--type TYPE |
Filter by snapshot type (can be repeated) |
--min-age DURATION |
Only backup snapshots older than duration (e.g., '1h') |
--compress METHOD |
Compression method (none, zstd, gzip, lz4) |
--rate-limit RATE |
Bandwidth limit (e.g., '10M', '1G') |
--ssh-sudo |
Use sudo on remote host |
--ssh-key FILE |
SSH private key file |
--ssh-auth-sock PATH |
Explicit ssh-agent socket (overrides auto-discovery) |
--progress |
Show progress bars |
--no-progress |
Disable progress bars |
Examples:
# Backup all snapshots for 'root' config to local path
btrfs-backup-ng snapper backup root /mnt/backup/root
# Backup to remote server
btrfs-backup-ng snapper backup root ssh://backup@server:/backups/root
# Backup only single-type snapshots with compression
btrfs-backup-ng snapper backup root /mnt/backup --type single --compress zstd
# Backup snapshots older than 1 hour
btrfs-backup-ng snapper backup root /mnt/backup --min-age 1h
# Backup to a raw target on a non-btrfs destination (USB, NFS, SMB)
btrfs-backup-ng snapper backup root raw:///mnt/usb/backups/root --compress zstd
# Backup to a remote raw target over SSH
btrfs-backup-ng snapper backup root raw+ssh://backup@server/mnt/nas/root --compress zstd
# Dry run to see what would be backed up
btrfs-backup-ng snapper backup root /mnt/backup --dry-runBackup Directory Layout:
Backups use snapper's native directory layout:
/mnt/backup/root/.snapshots/
├── 559/
│ ├── info.xml
│ └── snapshot/ # btrfs subvolume
├── 560/
│ ├── info.xml
│ └── snapshot/
└── 561/
├── info.xml
└── snapshot/
Show backup sync status between source and destination.
btrfs-backup-ng snapper status [OPTIONS]Options:
| Option | Description |
|---|---|
--config NAME |
Check specific config only |
--target PATH |
Check backup status at target |
--json |
Output in JSON format |
Examples:
# Show local snapshot counts
btrfs-backup-ng snapper status
# Show backup status for specific target
btrfs-backup-ng snapper status --target /mnt/backup/rootRestore snapper snapshots from backup.
btrfs-backup-ng snapper restore SOURCE [CONFIG] [OPTIONS]Arguments (positional, in this order):
| Argument | Description |
|---|---|
SOURCE |
Backup source: a local path, ssh://user@host:/path (btrfs), or raw://path / raw+ssh://user@host/path (non-btrfs) |
CONFIG |
Local snapper config to restore into (e.g. root, home). Required to restore; optional with --list (listing only reads the source) |
Options:
| Option | Description |
|---|---|
-l, --list |
List available backups instead of restoring |
-s, --snapshot NUM |
Restore snapshot by number (can be repeated). On a reused (duplicate) number, restores the newest by date and warns |
--backup-name NAME |
Restore the exact raw backup by name from --list (can be repeated); reaches an older copy when a snapper number was reused |
--date DATE |
Restrict the selection to backups whose date matches DATE (YYYY-MM-DD[ HH:MM:SS] prefix; an ISO-8601 T separator is accepted); disambiguates a reused --snapshot NUM |
-a, --all |
Restore all backups |
--from-config NAME |
Only restore backups that were made from this snapper config (overrides the target CONFIG for matching) |
--dry-run |
Show what would be done without making changes |
--ssh-sudo |
Use sudo on the remote host (ssh:// / raw+ssh://); required to read a root-owned target written with --ssh-sudo |
--ssh-key FILE |
SSH private key file |
--ssh-auth-sock PATH |
Explicit ssh-agent socket (overrides auto-discovery; useful under sudo) |
--ssh-host-key-policy {accept-new,strict} |
Host-key verification policy (default accept-new) |
--skip-remote-lock |
Proceed when the pin on the backup cannot be recorded on its location (a remote lock directory or a local lock file); for a location you can read but not write. A medium mounted read-only needs it not: no pin is asked for there |
--gpg-keyring PATH |
GPG keyring to decrypt an encrypted raw snapper backup (must match the keyring it was encrypted for) |
--openssl-cipher CIPHER |
OpenSSL cipher fallback for a legacy raw backup whose .meta sidecar does not record one (modern sidecars are authoritative) |
--json |
Output in JSON format (for --list) |
Examples:
# List available backups
btrfs-backup-ng snapper restore /mnt/backup/root --list
# Restore specific snapshot
btrfs-backup-ng snapper restore /mnt/backup/root root --snapshot 559
# Restore multiple snapshots
btrfs-backup-ng snapper restore /mnt/backup/root root --snapshot 559 --snapshot 560
# Restore all snapshots
btrfs-backup-ng snapper restore /mnt/backup/root root --all
# Dry run
btrfs-backup-ng snapper restore /mnt/backup/root root --all --dry-run
# Restore a root-owned raw+ssh:// backup (written with --ssh-sudo): pass it again
btrfs-backup-ng snapper restore raw+ssh://user@host/backups/root root --snapshot 559 --ssh-sudo
# Reused snapper number: restore an OLDER copy by exact name (from --list) ...
btrfs-backup-ng snapper restore /mnt/backup/root root --backup-name root-559-20240101-120000
# ... or by number + date
btrfs-backup-ng snapper restore /mnt/backup/root root --snapshot 559 --date 2024-01-01Note — snapper daemon cache. A restored snapshot is written directly into
.snapshots/{N}/(not viasnapper create), sosnapperddoes not see it until it reloads. Restart it (sudo systemctl restart snapperd, or reboot) after a restore before usingsnapper diff,snapper undochange, or a rollback against the restored snapshot; the restore command prints a reminder. The on-disk snapshot is complete and correct — this only affects the daemon's in-memory view. Restored slots use snapper's native0755permissions on.snapshots/{N}/.
Note — duplicate snapper numbers. Snapper reuses snapshot numbers after a prune, so a
raw:///raw+ssh://target can accumulate two retained backups with the same number (they differ by the date in the backup name).--snapshot Nrestores the newest of them by date and warns. To reach an older copy, use--backup-name NAME(the unique name is shown by--list, and in--list --jsonasbackup_name) or narrow--snapshot Nwith--date YYYY-MM-DD.--listshows a NAME column whenever names are available.
Generate TOML configuration for snapper-managed volumes.
btrfs-backup-ng snapper generate-config [OPTIONS]Options:
| Option | Description |
|---|---|
--config NAME |
Generate for specific config(s) (can be repeated) |
--target PATH |
Include target in generated config |
--type TYPE |
Snapshot types to include (default: single) |
--min-age DURATION |
Minimum snapshot age (default: 1h) |
--ssh-sudo |
Enable ssh_sudo in target config |
-o, --output FILE |
Write to file instead of stdout |
--append FILE |
Append to existing config file |
--json |
Output in JSON format |
Examples:
# Generate config for all snapper volumes
btrfs-backup-ng snapper generate-config
# Generate for specific config with target
btrfs-backup-ng snapper generate-config root --target ssh://backup@server:/backups
# Write to file
btrfs-backup-ng snapper generate-config -o ~/.config/btrfs-backup-ng/snapper.toml
# Append to existing config
btrfs-backup-ng snapper generate-config --append ~/.config/btrfs-backup-ng/config.tomlExample Output:
# Snapper volume configuration
# Generated by: btrfs-backup-ng snapper generate-config
[[volumes]]
path = "/"
source = "snapper"
[volumes.snapper]
config_name = "root"
include_types = ["single"]
min_age = "1h"
[[volumes.targets]]
path = "ssh://backup@server:/backups/root"
ssh_sudo = trueInspect and maintain raw-target backups directly. Raw targets hold btrfs send streams as files, each with an authoritative .meta sidecar.
btrfs-backup-ng raw SUBCOMMAND [OPTIONS]Mutating operations on a local raw:// target (a backup's commit, prune, backfill-metadata, and encrypt) hold an exclusive per-target lock so they cannot race each other; concurrent operations serialize rather than corrupt one another. The lock is an empty .btrfs-backup-ng.lock file kept in the target directory — it is created 0600, is safe to leave in place, and is never listed as a backup. Because the lock lives inside the target directory, that directory must not be writable by untrusted users. raw+ssh:// targets are serialized too. The lock is a directory under
<target>/.btrfs-backup-ng.locks/, created with mkdir — atomic, so exactly one of
any number of racing creators wins — and kept alive by a heartbeat, so a holder that
dies is broken by the next contender rather than blocking the target until someone
cleans up by hand. It holds across processes and across machines. See
Remote Locks.
List the backups a raw target holds, read from each stream's .meta sidecar (falling back to filename inference for legacy streams written before sidecars existed).
btrfs-backup-ng raw list TARGET [OPTIONS]Arguments:
| Argument | Description |
|---|---|
TARGET |
Raw target: raw://PATH, raw+ssh://[USER@]HOST/PATH, or a plain path (treated as raw://) |
Options:
| Option | Description |
|---|---|
--json |
Output the authoritative sidecar documents as JSON for scripting |
--ssh-sudo |
Use sudo for remote commands on a raw+ssh:// target |
Example:
btrfs-backup-ng raw list raw:///mnt/usb/backups
btrfs-backup-ng raw list raw+ssh://backup@nas/backups --ssh-sudoExample Output:
Raw target: raw:///mnt/usb/backups (2 snapshots)
NAME CREATED SIZE ENC COMPRESS CIPHER ORIGIN
root.20260101T120000 2026-01-01T12:00:00Z 1.2 GiB openssl_enc zstd aes-256-cbc native-write
home.20260101T120000 2026-01-01T12:00:00Z 340 MiB - - - native-write
Verify raw backups by recomputing each stream's sha256 and comparing it to the checksum recorded in its .meta sidecar. For a raw+ssh:// target the hash is computed on the remote host, so streams are not re-downloaded.
btrfs-backup-ng raw verify TARGET [OPTIONS]Arguments:
| Argument | Description |
|---|---|
TARGET |
Raw target: raw://PATH, raw+ssh://[USER@]HOST/PATH, or a plain path |
Options:
| Option | Description |
|---|---|
--snapshot NAME |
Verify only the named snapshot |
--json |
Output per-snapshot results as JSON |
--ssh-sudo |
Use sudo for remote commands on a raw+ssh:// target |
Per-snapshot status: ok (matches), corrupt (the stored stream differs from what was backed up), error (the stream could not be read/hashed), or unverifiable (no checksum was recorded — a legacy backup, or a best-effort write-time seal that failed). Exit status is 1 if any snapshot is corrupt or errored, else 0.
raw verify confirms a stream still matches the checksum recorded in its own .meta sidecar — an integrity/consistency check, not an authenticity one: an attacker who can rewrite both the stream and its sidecar can make a corrupted backup report ok. For a raw+ssh:// target the hash is recomputed on the (untrusted) remote host, so an ok verdict only confirms the recorded checksum and cannot detect corruption or tampering introduced by a compromised target. For tamper-evident verification, verify a locally-mounted copy (raw://), where the stream is hashed with your own host's kernel after dropping its page cache.
Example Output:
Raw target: raw:///mnt/usb/backups (verifying 2 snapshots)
OK root.20260101T120000
CORRUPT home.20260101T120000
1 ok, 1 corrupt, 0 error, 0 unverifiable
Write authoritative .meta sidecars for legacy raw streams that have none — backups written before sidecars existed (or by another tool such as btrbk). New backups already write their sidecar automatically; this is only for pre-existing streams.
btrfs-backup-ng raw backfill-metadata TARGET [OPTIONS]Arguments:
| Argument | Description |
|---|---|
TARGET |
Raw target: raw://PATH, raw+ssh://[USER@]HOST/PATH, or a plain path |
Options:
| Option | Description |
|---|---|
--dry-run |
Show which streams would be backfilled without writing anything |
--json |
Output per-stream results as JSON |
--ssh-sudo |
Use sudo for remote commands on a raw+ssh:// target |
Each backfilled sidecar records the pipeline inferred from the filename (compression/encryption) and a sha256 of the stream as it exists now, and is stamped provenance_origin=backfill with stream_completeness=unknown. A legacy stream's completeness cannot be proven (it might have been truncated), so a backfilled sidecar is marked as a reconstructed, non-authoritative record — the checksum lets raw verify detect later corruption, but does not confirm the original backup was complete. Exit status is 1 if any sidecar write failed, else 0.
On a local raw:// target, backfill-metadata holds the per-target lock while it works, so a concurrent backup or prune serializes against it; it also re-checks for a sidecar immediately before writing, so a concurrently-committed backup is never overwritten. A raw+ssh:// target takes the same lock, recorded on the remote target itself, so a
concurrent backup or prune — from another process, or another machine — serializes
against it rather than racing it.
Example Output:
Raw target: raw:///mnt/usb/backups (2 legacy streams without a sidecar)
BACKFILLED root.20240115T120000
BACKFILLED home.20240115T120000
2 backfilled, 0 error
Note: backfilled sidecars are marked stream_completeness=unknown -- a legacy stream cannot be proven complete.
Encrypt raw streams that were written as plaintext — remediation for backups affected by GHSA-vr25-6vrh-869j, where an encrypt= config on a raw target could silently write plaintext (fixed in 0.8.4). Runs locally only so the passphrase/keys never leave the host; for a remote target, mount it and use a raw:// path.
btrfs-backup-ng raw encrypt TARGET --encrypt {gpg|openssl_enc} [OPTIONS]Options:
| Option | Description |
|---|---|
--encrypt {gpg,openssl_enc} |
Encryption method to apply (required) |
--gpg-recipient KEYID |
GPG recipient (required with --encrypt gpg) |
--openssl-cipher CIPHER |
OpenSSL cipher for openssl_enc (default aes-256-cbc) |
--shred |
Remove each plaintext file after a verified decrypt proof |
--yes |
Do not prompt before removing plaintext (for scripts; with --shred) |
--dry-run |
Show which plaintext streams would be encrypted, without changes |
--json |
Per-stream results as JSON |
For each plaintext stream, raw encrypt writes an encrypted copy alongside it (a new authoritative sidecar with provenance.origin=remediation and remediated_from), then runs a live decrypt-to-identical proof — it decrypts the new stream and confirms it is byte-identical to the plaintext.
The plaintext is removed only when that proof passes and --shred was given (by default the plaintext is kept). For gpg, the proof needs the secret key on this host; if it is absent, the encrypted copy is written but the plaintext is kept.
Erasure caveat.
--shredremoves the plaintext with a plainunlink. On copy-on-write filesystems (btrfs) and on SSDs, this does not securely erase the underlying blocks (btrfs writes new blocks; SSD controllers remap them). True physical erasure relies on device-leveltrim/discardor full-disk encryption at rest, which this tool cannot provide — and prior exposure (older backup media, indexes) cannot be undone.
Example:
# encrypt plaintext backups with openssl (BTRFS_BACKUP_PASSPHRASE set), keep plaintext:
btrfs-backup-ng raw encrypt raw:///mnt/usb/backups --encrypt openssl_enc
# ...then remove the plaintext after the verified proof:
btrfs-backup-ng raw encrypt raw:///mnt/usb/backups --encrypt openssl_enc --shredStated by what a path is, not by where it was typed, so there are no exceptions. A backup location (a source, a target on any transport, an absolute snapshot base -- from the configuration file, from snapper backup TARGET, or from legacy mode's arguments) is a statement that something is there and is never created. An output location (where a command was asked to put something new) is created. The program's own state is created. Anything below a location that exists is created one level at a time, never the location itself.
MUST EXIST (refused, nothing created) CREATED ON REQUEST (output locations)
[[volumes]] path restore DESTINATION / --to
[[volumes.targets]] path (all schemes) verify --temp-dir
snapshot_dir when absolute config init -o, completions install,
snapper backup TARGET manpages install
legacy SOURCE, DESTINATION, absolute -f
CREATED BELOW AN EXISTING LOCATION, ONE LEVEL AT A TIME
<target>/.btrfs-backup-ng/snapshots the target's own bookkeeping
<volume>/<snapshot_dir> a RELATIVE snapshot_dir
<snapshot_dir>/<volume name> under an ABSOLUTE snapshot_dir
<target>/.snapshots/<n> a snapper backup's slots
<target>/.btrfs-backup-ng.locks locks on a remote target
The refusal names the path and the remedy. Create a missing directory once by hand (mkdir -p PATH, or ssh HOST 'mkdir -p PATH' for a remote target), or mount the filesystem that should hold it, and run again.
The --fs-checks option controls how btrfs-backup-ng validates source and destination paths:
| Mode | Behavior |
|---|---|
auto (default) |
Warn about issues but continue operation |
strict |
Error out on any check failure |
skip |
Bypass all filesystem checks |
The --no-fs-checks flag is an alias for --fs-checks=skip.
Examples:
# Default auto mode - warns but continues
btrfs-backup-ng run
# Strict mode for production
btrfs-backup-ng run --fs-checks=strict
# Skip checks for backup directories
btrfs-backup-ng restore --list /mnt/backup --no-fs-checksWhen the first argument is a path (not a subcommand), btrfs-backup-ng runs in legacy mode for backwards compatibility. The source, the destination and an absolute -f/--snapshot-folder are backup locations: they must exist and are not created; a relative folder is created under the source. See Paths That Must Exist, and Paths That Are Created.
btrfs-backup-ng [OPTIONS] SOURCE DESTINATIONLegacy Options:
| Option | Description |
|---|---|
-n, --num-snapshots N |
Number of snapshots to keep |
-f, --snapshot-folder DIR |
Folder the snapshots are taken into: relative to the source, or absolute (must exist). Default .snapshots inside the source |
--accept-full-send |
Start a new chain in --snapshot-folder although <source>/.snapshots already holds one, accepting one full send to every destination. Needed once at most; see below |
--no-incremental |
Disable incremental transfers |
--ssh-sudo |
Use sudo on SSH remote |
--ssh-username USER |
SSH username |
--ssh-identity-file FILE |
SSH private key |
--convert-rw |
Convert to read-write before deletion |
--fs-checks MODE |
Filesystem check mode: auto, strict, skip |
--no-fs-checks |
Skip filesystem checks (alias for --fs-checks=skip) |
--no-check-space |
Disable pre-flight space checking |
--force |
Proceed despite insufficient space warnings |
--safety-margin PERCENT |
Safety margin for space checks (default: 10%) |
Examples:
# Basic local backup
btrfs-backup-ng /home /mnt/backup/home
# Remote backup with SSH
btrfs-backup-ng /home ssh://backup@server:/backups/home
# With options
btrfs-backup-ng --ssh-sudo --num-snapshots 10 /home ssh://user@host:/backupUntil this release -f/--snapshot-folder had no effect on placement: every legacy-mode snapshot went to <source>/.snapshots. That is now the default, so a run that never passed -f sees no change. A run that did pass -f, and whose chain is therefore in <source>/.snapshots while the named folder is empty, is refused rather than started as a new chain (which would make the next transfer a full send). The refusal lists the remedies: move the chain into the folder (mv <source>/.snapshots/<prefix>* <folder>/, same filesystem), pass -f <source>/.snapshots to keep it where it is, or add --accept-full-send once.
When backing up to a remote host via SSH, there are several authentication scenarios.
Key-based authentication (recommended):
For key-based SSH authentication, load your SSH key into ssh-agent (ssh-add). Backups run
as root (btrfs send/receive need it), and sudo strips SSH_AUTH_SOCK — but a
passphrase-protected key can only sign via its agent. btrfs-backup-ng therefore
auto-discovers your ssh-agent socket across common locations (including ~/.ssh/agent/,
/tmp/ssh-*, /run/user/<uid>/keyring/ssh, gpg/gcr agents) and uses it, preferring a
socket that actually has keys loaded. In most setups a plain sudo btrfs-backup-ng run
just works.
If your agent lives somewhere unusual, or you run several agents, pin the socket explicitly
(highest precedence, then a preserved SSH_AUTH_SOCK, then auto-discovery):
# 1. Explicit socket in the target config:
# [[volumes.targets]]
# ssh_auth_sock = "/path/to/agent.sock"
#
# 2. Environment override (works for every command):
export BTRFS_BACKUP_SSH_AUTH_SOCK="$SSH_AUTH_SOCK"
sudo --preserve-env=BTRFS_BACKUP_SSH_AUTH_SOCK btrfs-backup-ng run
#
# 3. Preserve your existing agent socket through sudo:
sudo --preserve-env=SSH_AUTH_SOCK btrfs-backup-ng run # or: sudo -E ...
#
# 4. On one-off commands: restore/verify/estimate/snapper accept --ssh-auth-sock PATHThe tool also detects SSH keys in ~/.ssh/ (id_ed25519, id_rsa, id_ecdsa) for the original
user when running via sudo. If authentication fails, the error message names the exact
remediation for your situation. A passphrase-less key avoids the agent requirement entirely.
Password authentication:
Set ssh_password_auth = true in your target configuration to enable interactive SSH password prompts:
[[volumes.targets]]
path = "ssh://user@host:/backup"
ssh_password_auth = truebtrfs commands require root privileges. For remote backups, configure sudo access on the remote host.
Passwordless sudo (recommended for automated backups):
Add this to /etc/sudoers.d/btrfs-backup on the remote host:
# Allow btrfs commands without password for backup user
username ALL=(ALL) NOPASSWD: /usr/bin/btrfs
Then enable sudo in your target configuration:
[[volumes.targets]]
path = "ssh://user@host:/backup"
ssh_sudo = truePassword-based sudo:
If passwordless sudo is not configured, the tool will prompt for the sudo password interactively, or you can set it via environment variable:
export BTRFS_BACKUP_SUDO_PASSWORD="your-password"
btrfs-backup-ng runFor fully automated backups without any password prompts:
- Local machine: Run with
sudo -Eto preserve SSH agent - Remote machine: Configure passwordless sudo for btrfs
# On remote host, create /etc/sudoers.d/btrfs-backup:
# myuser ALL=(ALL) NOPASSWD: /usr/bin/btrfs
# On local machine, run backup:
sudo -E btrfs-backup-ng runConfiguration:
[[volumes.targets]]
path = "ssh://myuser@backupserver:/mnt/backups/myvolume"
ssh_sudo = truebtrfs-backup-ng verifies the remote host's SSH key on every connection.
| Policy | Behavior |
|---|---|
accept-new (default) |
Trust a host's key on first contact and pin it; refuse to connect if that key later changes (the usual man-in-the-middle signal). |
strict |
Only connect to hosts already present in known_hosts; an unknown host is refused. |
Set it in a target's config or per run:
[[volumes.targets]]
path = "ssh://backup@server:/mnt/backups/vol"
ssh_host_key_policy = "strict" # or "accept-new" (default)btrfs-backup-ng restore --ssh-host-key-policy strict ...An unrecognized value is a configuration error (fails closed — it never silently downgrades).
When running 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 (for example an explicit StrictHostKeyChecking) take precedence over the
tool's default.
SSH ControlMaster sockets are created in an unpredictable, private (0700) directory (preferring
$XDG_RUNTIME_DIR) and removed when the connection closes, so a local user cannot hijack the
multiplexed connection to your backup host. The internal command lock and the raw+ssh remote
metadata write likewise avoid predictable, symlink-plantable paths.
The command lock (btrfs-backup-ng-<uid>/command.lock under $XDG_RUNTIME_DIR or the temp
directory) is left in place after a run, deliberately. It is an flock, which excludes by
inode: removing the file while another process waits on it would let that process and a
newcomer each hold a lock on a different file at the same path, and both would proceed.
Upgrade note:
raw+ssh://targets now set an explicitaccept-newpolicy (previously they inherited the ambient SSH default, which under batch mode accidentally refused unknown hosts). If you relied on that refuse-unknown behavior, setssh_host_key_policy = "strict".
| Variable | Description |
|---|---|
BTRFS_BACKUP_SUDO_PASSWORD |
Sudo password for remote hosts (avoids interactive prompt) |
BTRFS_BACKUP_SSH_PASSWORD |
Enable SSH password authentication |
BTRFS_BACKUP_PASSWORDLESS_ONLY |
Only use passwordless sudo (fail if password required) |
BTRFS_BACKUP_LOG_LEVEL |
Console log level when nothing else says (DEBUG, INFO, WARNING, ERROR). The lowest-precedence source: --debug/--btrfs-debug/-q/-v win over it, and [global] quiet/verbose/btrfs_debug in the configuration win over it too. Any other value is ignored, with a warning. |
SSH_AUTH_SOCK |
SSH agent socket (preserve with sudo -E) |
| Code | Description |
|---|---|
| 0 | Success |
| 1 | Error (configuration, runtime, or transfer failure) |
btrfs-backup-ng searches for configuration files in the following locations, in priority order:
| Priority | Location | Description |
|---|---|---|
| 1 (highest) | -c /path/to/config.toml |
Explicit path via command line |
| 2 | ~/.config/btrfs-backup-ng/config.toml |
User-specific configuration |
| 3 | /etc/btrfs-backup-ng/config.toml |
System-wide configuration |
The first configuration file found is used. If no configuration file exists and none is specified, commands that require configuration will display an error.
User configuration:
mkdir -p ~/.config/btrfs-backup-ng
btrfs-backup-ng config init > ~/.config/btrfs-backup-ng/config.tomlSystem-wide configuration:
sudo mkdir -p /etc/btrfs-backup-ng
btrfs-backup-ng config init | sudo tee /etc/btrfs-backup-ng/config.toml
sudo chmod 600 /etc/btrfs-backup-ng/config.tomlCheck which config is active:
btrfs-backup-ng config validate
# Shows: Validating: /path/to/active/config.toml| Method | Description | Speed | Ratio |
|---|---|---|---|
none |
No compression | Fastest | 1:1 |
lz4 |
Very fast compression | Very Fast | Low |
lzop |
Fast compression | Fast | Low |
pigz |
Parallel gzip | Fast | Medium |
gzip |
Standard compression | Medium | Medium |
zstd |
Modern compression (recommended) | Medium | High |
Raw targets write btrfs send streams directly to files instead of using btrfs receive. This enables backups to non-btrfs filesystems (NFS, SMB, cloud storage) with optional compression and encryption.
| Scheme | Description |
|---|---|
raw:///path |
Local raw target |
raw+ssh://user@host/path |
Remote raw target via SSH |
| Option | Description |
|---|---|
compress |
Compression: gzip, pigz, zstd, lz4, xz, lzo, bzip2, pbzip2 |
encrypt |
Encryption: gpg, openssl_enc |
gpg_recipient |
GPG key recipient (required for gpg) |
gpg_keyring |
Optional GPG keyring path |
openssl_cipher |
OpenSSL cipher (default: aes-256-cbc) |
[[volumes]]
path = "/home"
# Raw target with compression and GPG encryption
[[volumes.targets]]
path = "raw:///mnt/nas/backups/home"
compress = "zstd"
encrypt = "gpg"
gpg_recipient = "backup@example.com"
# Raw target over SSH
[[volumes.targets]]
path = "raw+ssh://backup@server/backups/home"
compress = "zstd"GPG (public-key, recommended):
[[volumes.targets]]
path = "raw:///mnt/backup"
encrypt = "gpg"
gpg_recipient = "backup@example.com"OpenSSL (symmetric, for btrbk migration):
[[volumes.targets]]
path = "raw:///mnt/backup"
encrypt = "openssl_enc"For OpenSSL encryption, set the passphrase via environment variable:
export BTRFS_BACKUP_PASSPHRASE="your_passphrase"
# or (btrbk compatible)
export BTRBK_PASSPHRASE="your_passphrase"Raw backups create predictable filenames:
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
Each stream file has a companion .meta file with JSON metadata for incremental chain tracking.
Bandwidth limits use a number with optional suffix:
| Suffix | Multiplier |
|---|---|
| (none) | Bytes per second |
K |
Kilobytes per second (1024) |
M |
Megabytes per second (1024^2) |
G |
Gigabytes per second (1024^3) |
Examples: 500K, 10M, 1G, 52428800 (50MB in bytes)
Retention durations use a number with suffix:
| Suffix | Duration |
|---|---|
m |
Minutes |
h |
Hours |
d |
Days |
w |
Weeks |
Examples: 30m, 6h, 1d, 2w