Swiss army knife cli tool for atomic, incremental, intelligent, feature-rich backups for btrfs
Project description
btrfs-backup-ng
A modern, feature-rich tool for automated BTRFS snapshot backup management with TOML configuration, time-based retention policies, and robust SSH transfer support.
Heritage
This project is a continuation of the btrfs-backup lineage:
- 2014: Originally created by Chris Lawrence
- 2017: Maintained and enhanced by Robert Schindler
- 2024-present: Continued development by Michael Berry as btrfs-backup-ng
See the LICENSE file for full copyright attribution.
Features
- TOML Configuration: Clean, validated configuration files (no custom syntax)
- Subcommand CLI: Modern interface with
run,snapshot,transfer,prune,restore,verify,estimate,list,status - Disaster Recovery: Built-in restore command to pull backups back to local systems
- Backup Verification: Multi-level integrity checks (metadata, stream, full restore test)
- Time-based Retention: Intuitive policies (hourly, daily, weekly, monthly, yearly)
- Rich Progress Bars: Real-time transfer progress with speed, ETA, and percentage
- Parallel Execution: Concurrent volume and target transfers
- Stream Compression: zstd, gzip, lz4, pigz, lzop support
- Bandwidth Throttling: Rate limiting for remote transfers
- Transaction Logging: Structured JSON logs for auditing and automation
- Email & Webhook Notifications: Alerts on backup success/failure
- Systemd Integration: Built-in timer/service installation
- btrbk Migration: Import existing btrbk configurations
- Robust SSH: Password fallback, sudo support, Paramiko integration
- Legacy Compatibility: Original CLI still works
Installation
From Source
git clone https://github.com/berrym/btrfs-backup-ng.git
cd btrfs-backup-ng
pip install -e .
Requirements
- Python 3.11+
- BTRFS utilities (
btrfs-progs) - SSH client (for remote backups)
Optional Dependencies
# Compression support
sudo dnf install zstd lz4 pigz # Fedora/RHEL
sudo apt install zstd lz4 pigz # Debian/Ubuntu
# Bandwidth throttling
sudo dnf install pv # Fedora/RHEL
sudo apt install pv # Debian/Ubuntu
Shell Completions
Shell completion scripts are available in the completions/ directory for bash, zsh, and fish. See completions/README.md for installation instructions.
Man Pages
Manual pages are available in the man/ directory. See man/README.md for installation instructions. View without installing:
man ./man/btrfs-backup-ng.1
Quick Start
1. Create a Configuration File
# Option A: Interactive wizard (recommended for new users)
btrfs-backup-ng config init --interactive -o ~/.config/btrfs-backup-ng/config.toml
# Option B: Generate example config and edit manually
btrfs-backup-ng config init > ~/.config/btrfs-backup-ng/config.toml
2. Edit the Configuration
[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"
rate_limit = "50M"
3. Run Backups
# Full backup (snapshot + transfer + prune)
btrfs-backup-ng run
# Or individual operations
btrfs-backup-ng snapshot
btrfs-backup-ng transfer
btrfs-backup-ng prune
4. Set Up Automated Backups
# Install systemd timer (hourly backups)
sudo btrfs-backup-ng install --timer=hourly
# Or custom schedule (every 15 minutes)
sudo btrfs-backup-ng install --oncalendar='*:0/15'
# User-level timer (no sudo needed)
btrfs-backup-ng install --user --timer=daily
Commands
| Command | Description |
|---|---|
run |
Execute full backup (snapshot + transfer + prune) |
snapshot |
Create snapshots only |
transfer |
Transfer existing snapshots to targets |
prune |
Apply retention policies |
restore |
Restore snapshots from backup location |
verify |
Verify backup integrity (metadata, stream, or full test) |
estimate |
Estimate backup transfer sizes before execution |
list |
Show snapshots and backups |
status |
Show job status and statistics |
config validate |
Validate configuration file |
config init |
Generate example configuration (use -i for interactive wizard) |
config import |
Import btrbk configuration |
install |
Install systemd timer/service |
uninstall |
Remove systemd timer/service |
Common Options
# Dry run (show what would be done)
btrfs-backup-ng run --dry-run
btrfs-backup-ng prune --dry-run
# Override compression and rate limit
btrfs-backup-ng transfer --compress=zstd --rate-limit=10M
# Progress bar control (auto-detected by default)
btrfs-backup-ng run --progress # Force progress bars
btrfs-backup-ng run --no-progress # Disable progress bars
# Parallel execution
btrfs-backup-ng run --parallel-volumes=2 --parallel-targets=3
# Specify config file
btrfs-backup-ng -c /path/to/config.toml run
# Verbose output
btrfs-backup-ng -v run
# View transaction history
btrfs-backup-ng status --transactions
Configuration Reference
Global Settings
[global]
snapshot_dir = ".snapshots" # Relative to volume or absolute
timestamp_format = "%Y%m%d-%H%M%S" # Snapshot timestamp format
incremental = true # Use incremental transfers
log_file = "/var/log/btrfs-backup-ng.log" # Optional rotating log file
transaction_log = "/var/log/btrfs-backup-ng.jsonl" # Optional JSON transaction log
parallel_volumes = 2 # Concurrent volume backups
parallel_targets = 3 # Concurrent target transfers
quiet = false # Suppress non-essential output
verbose = false # Enable verbose output
Logging
Two logging systems are available:
File Logging (log_file): Human-readable rotating log file (10MB max, 5 backups)
2026-01-04 13:10:51 [INFO] btrfs-backup-ng: Creating snapshot...
2026-01-04 13:10:57 [INFO] btrfs-backup-ng: Transfer completed successfully
Transaction Logging (transaction_log): Structured JSONL for auditing and automation
{"timestamp": "2026-01-04T18:10:57+00:00", "action": "transfer", "status": "completed", "snapshot": "home-20260104", "duration_seconds": 5.78, "size_bytes": 2175549440}
View transaction history via CLI:
btrfs-backup-ng status --transactions # Show recent transactions
btrfs-backup-ng status -t -n 20 # Show last 20 transactions
Notifications
btrfs-backup-ng can send notifications on backup completion or failure via email (SMTP) or webhooks. This is particularly useful for automated/scheduled backups.
Email Notifications
[global.notifications.email]
enabled = true
smtp_host = "smtp.example.com"
smtp_port = 587
smtp_tls = "starttls" # "ssl" (port 465), "starttls" (port 587), or "none"
smtp_user = "alerts@example.com"
smtp_password = "your-password"
from_addr = "btrfs-backup-ng@example.com"
to_addrs = ["admin@example.com", "ops@example.com"]
on_success = false # Don't notify on success (default)
on_failure = true # Notify on failure (default)
SMTP Port Reference:
| Port | TLS Mode | Description |
|---|---|---|
| 465 | ssl |
Implicit TLS (SMTPS) |
| 587 | starttls |
Explicit TLS (submission) |
| 25 | none |
Plain text (local mail only) |
Example: Gmail SMTP
[global.notifications.email]
enabled = true
smtp_host = "smtp.gmail.com"
smtp_port = 587
smtp_tls = "starttls"
smtp_user = "your-email@gmail.com"
smtp_password = "your-app-password" # Use App Password, not account password
from_addr = "your-email@gmail.com"
to_addrs = ["your-email@gmail.com"]
on_failure = true
Example: Local Postfix/Sendmail
[global.notifications.email]
enabled = true
smtp_host = "localhost"
smtp_port = 25
smtp_tls = "none"
from_addr = "btrfs-backup-ng@myserver.local"
to_addrs = ["root@myserver.local"]
on_failure = true
Webhook Notifications
Send JSON payloads to any HTTP endpoint (Slack, Discord, PagerDuty, custom services, etc.).
[global.notifications.webhook]
enabled = true
url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX"
method = "POST" # POST or PUT
timeout = 30 # Request timeout in seconds
on_success = false
on_failure = true
# Optional custom headers
[global.notifications.webhook.headers]
Authorization = "Bearer your-token"
X-Custom-Header = "value"
Webhook Payload Format:
The webhook receives a JSON payload with full backup details:
{
"event_type": "backup_complete",
"status": "failure",
"timestamp": "2026-01-04T18:30:00+00:00",
"hostname": "myserver",
"summary": "Backup failed: 1 volumes failed",
"volumes_processed": 2,
"volumes_failed": 1,
"snapshots_created": 1,
"transfers_completed": 1,
"transfers_failed": 1,
"duration_seconds": 45.2,
"errors": ["Transfer to ssh://backup@remote:/backups: Connection refused"]
}
Example: Slack Incoming Webhook
Slack accepts the payload directly when using their Incoming Webhooks feature.
[global.notifications.webhook]
enabled = true
url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX"
on_failure = true
Example: Discord Webhook
Discord webhooks expect a specific format. Use a proxy or serverless function to transform the payload.
Example: ntfy.sh (Simple Push Notifications)
[global.notifications.webhook]
enabled = true
url = "https://ntfy.sh/your-topic"
method = "POST"
on_failure = true
[global.notifications.webhook.headers]
Title = "btrfs-backup-ng Alert"
Priority = "high"
Tags = "warning,backup"
Notification Events
Notifications are sent for these events:
| Event | Commands | Status Values |
|---|---|---|
backup_complete |
run, transfer |
success, partial, failure |
prune_complete |
prune |
success, partial, failure |
Status meanings:
success: All operations completed without errorspartial: Some operations succeeded, some failedfailure: All operations failed
Controlling When Notifications Are Sent
By default, notifications are only sent on failure. Configure per notification method:
# Email: notify on failure only (default)
[global.notifications.email]
enabled = true
on_success = false
on_failure = true
# Webhook: notify on both success and failure
[global.notifications.webhook]
enabled = true
on_success = true
on_failure = true
Testing Notifications
To test your notification configuration, run a backup with intentionally bad settings:
# Create a test config with a bad target
cat > /tmp/test-notify.toml << 'EOF'
[global.notifications.email]
enabled = true
smtp_host = "localhost"
smtp_port = 25
from_addr = "test@localhost"
to_addrs = ["root@localhost"]
on_failure = true
[[volumes]]
path = "/nonexistent"
[[volumes.targets]]
path = "/also/nonexistent"
EOF
# Run with the test config (will fail and trigger notification)
sudo btrfs-backup-ng -c /tmp/test-notify.toml run
Security Considerations
- SMTP passwords in config files: Use file permissions (
chmod 600) to protect your configuration file - Webhook URLs: Treat webhook URLs as secrets; they often provide unauthenticated access
- Environment variables: Future versions may support reading secrets from environment variables
# Protect config file with sensitive credentials
sudo chmod 600 /etc/btrfs-backup-ng/config.toml
sudo chown root:root /etc/btrfs-backup-ng/config.toml
Retention Policy
[global.retention]
min = "1d" # Keep all snapshots for at least 1 day
hourly = 24 # Keep 24 hourly snapshots
daily = 7 # Keep 7 daily snapshots
weekly = 4 # Keep 4 weekly snapshots
monthly = 12 # Keep 12 monthly snapshots
yearly = 0 # Don't keep yearly (0 = disabled)
Duration format: 30m (minutes), 2h (hours), 1d (days), 1w (weeks)
Volume Configuration
[[volumes]]
path = "/home" # Path to BTRFS subvolume
snapshot_prefix = "home-" # Prefix for snapshot names
snapshot_dir = ".snapshots" # Override global snapshot_dir
enabled = true # Enable/disable this volume
# Volume-specific retention (overrides global)
[volumes.retention]
daily = 14
weekly = 8
Target Configuration
[[volumes.targets]]
path = "/mnt/backup/home" # Local path
[[volumes.targets]]
path = "ssh://user@host:/backups" # SSH remote path
ssh_sudo = true # Use sudo on remote
ssh_port = 22 # SSH port
ssh_key = "~/.ssh/backup_key" # SSH private key
ssh_password_auth = true # Allow password fallback
compress = "zstd" # Compression (none|gzip|zstd|lz4|pigz|lzop)
rate_limit = "10M" # Bandwidth limit (K|M|G suffix)
require_mount = false # Require path to be a mount point (safety check)
Mount Verification (External Drive Safety)
When backing up to external drives or removable media, there's a common pitfall: if the drive isn't mounted, backups will silently write to the mount point directory on your root filesystem, consuming disk space and not actually backing up your data.
The require_mount option prevents this by verifying that the target path is an active mount point before starting any transfers.
# External USB drive backup with mount verification
[[volumes.targets]]
path = "/mnt/usb-backup"
require_mount = true # Fail if /mnt/usb-backup is not a mount point
When to use require_mount = true:
- External USB drives
- Removable media (SD cards, etc.)
- Network mounts (NFS, SMB) that may be disconnected
- Any target that might not always be available
Example error when drive is not mounted:
ERROR: Target /mnt/usb-backup is not mounted.
Ensure the drive is connected and mounted, or set require_mount = false.
Complete external drive configuration example:
[[volumes]]
path = "/home"
snapshot_prefix = "home"
[[volumes.targets]]
path = "/mnt/external-backup/home"
require_mount = true # Safety check - fail if drive not mounted
# Also backup to remote server (no mount check needed for SSH)
[[volumes.targets]]
path = "ssh://backup@server:/backups/home"
ssh_sudo = true
Note: require_mount only applies to local targets. It has no effect on SSH targets.
Configuration File Locations
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 in any location and none is specified with -c, commands that require configuration will display an error with instructions.
User Configuration
For personal workstations or when running backups as a regular user:
# Create user config directory
mkdir -p ~/.config/btrfs-backup-ng
# Generate example configuration
btrfs-backup-ng config init > ~/.config/btrfs-backup-ng/config.toml
# Edit to match your setup
$EDITOR ~/.config/btrfs-backup-ng/config.toml
# Run backups (config is found automatically)
sudo btrfs-backup-ng run
System-Wide Configuration
For servers, shared systems, or when using systemd timers:
# Create system config directory
sudo mkdir -p /etc/btrfs-backup-ng
# Generate example configuration
btrfs-backup-ng config init | sudo tee /etc/btrfs-backup-ng/config.toml
# Edit to match your setup
sudo $EDITOR /etc/btrfs-backup-ng/config.toml
# Set appropriate permissions (readable by root only for security)
sudo chmod 600 /etc/btrfs-backup-ng/config.toml
# Run backups (config is found automatically)
sudo btrfs-backup-ng run
Configuration Precedence
When both user and system configurations exist:
- User config takes precedence over system config
- Use
-cto explicitly select a different configuration - The
config validatecommand shows which file is being used
# See which config file is active
btrfs-backup-ng config validate
# Output: Validating: /home/user/.config/btrfs-backup-ng/config.toml
# Force use of system config
sudo btrfs-backup-ng -c /etc/btrfs-backup-ng/config.toml run
Configuration Commands
Interactive Configuration Wizard
The easiest way to create a new configuration is the interactive wizard:
# Run the wizard
btrfs-backup-ng config init --interactive
# Run the wizard and save directly to a file
btrfs-backup-ng config init -i -o ~/.config/btrfs-backup-ng/config.toml
The wizard guides you through:
- Global settings: Snapshot directory, timestamp format, incremental mode
- Parallelism: Max concurrent volumes and targets
- Retention policy: How many hourly, daily, weekly, monthly, yearly snapshots to keep
- Notifications: Email (SMTP) and webhook settings
- Volumes: Which directories to back up
- Targets: Where to send backups (local paths or SSH URLs)
The wizard automatically suggests appropriate options:
- Prompts for
require_mountwhen targeting/mnt/paths (external drive safety) - Prompts for
ssh_sudowhen targeting SSH URLs
Press Ctrl+C at any time to cancel.
Generate Example Configuration
For manual editing, generate an example configuration with comments:
# Output to stdout
btrfs-backup-ng config init
# Save to file
btrfs-backup-ng config init -o config.toml
Validate Configuration
Check your configuration for errors before running backups:
btrfs-backup-ng config validate
# Validate a specific file
btrfs-backup-ng config validate -c /path/to/config.toml
Import btrbk Configuration
Migrate from btrbk by importing your existing configuration:
# Convert and output 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.toml
See Migrating from btrbk for more details.
Recommended Setup for Automated Backups
For production servers with systemd timers:
# 1. Create system-wide configuration (interactive wizard)
sudo mkdir -p /etc/btrfs-backup-ng
sudo btrfs-backup-ng config init -i -o /etc/btrfs-backup-ng/config.toml
sudo chmod 600 /etc/btrfs-backup-ng/config.toml
# Or generate example and edit manually:
# btrfs-backup-ng config init | sudo tee /etc/btrfs-backup-ng/config.toml
# sudo chmod 600 /etc/btrfs-backup-ng/config.toml
# 2. Edit configuration (if not using interactive wizard)
sudo $EDITOR /etc/btrfs-backup-ng/config.toml
# 3. Validate configuration
sudo btrfs-backup-ng config validate
# 4. Test manually first
sudo btrfs-backup-ng run --dry-run
sudo btrfs-backup-ng run
# 5. Install and enable systemd timer
sudo btrfs-backup-ng install --timer=hourly
sudo systemctl daemon-reload
sudo systemctl enable --now btrfs-backup-ng.timer
# 6. Verify timer is active
systemctl list-timers btrfs-backup-ng.timer
Migrating from btrbk
Import your existing btrbk configuration:
btrfs-backup-ng config import /etc/btrbk/btrbk.conf -o config.toml
The importer will:
- Convert btrbk's custom syntax to TOML
- Translate retention policies
- Warn about common btrbk pitfalls
- Suggest improvements
Key Differences from btrbk
| Feature | btrbk | btrfs-backup-ng |
|---|---|---|
| Config format | Custom syntax | TOML (standard) |
| Config validation | Runtime errors | Pre-flight validation |
| Indentation | Ignored (confusing) | TOML is explicit |
| Language | Perl | Python |
| CLI output | Plain text | Rich formatting |
| SSH handling | External ssh | Native Paramiko option |
SSH Remote Backup Setup
This section provides a complete guide for setting up passwordless SSH backups to a remote server. For fully automated backups, you need:
- SSH key authentication (no password prompts)
- Passwordless sudo on the remote for btrfs commands
- A btrfs filesystem on the remote to receive backups
Step 1: Create a Dedicated Backup User (Remote Server)
On the remote backup server, create a dedicated user for backups:
# Create backup user with no password (key-only auth)
sudo useradd -m -s /bin/bash backup
# Create the backup directory on a btrfs filesystem
sudo mkdir -p /mnt/backups
sudo chown backup:backup /mnt/backups
# Verify the backup location is on a btrfs filesystem
df -T /mnt/backups | grep btrfs || echo "WARNING: Not a btrfs filesystem!"
Step 2: Configure Passwordless Sudo (Remote Server)
btrfs send/receive requires root privileges. On the remote server, configure sudo:
Create a dedicated sudoers file in /etc/sudoers.d/ (preferred over editing /etc/sudoers directly):
# Create sudoers drop-in file for backup user
sudo visudo -f /etc/sudoers.d/btrfs-backup
Add one of these configurations:
# Option 1: Full btrfs access (simplest, recommended)
backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs
# Option 2: Restricted to specific btrfs subcommands (more secure)
backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs receive *
backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs subvolume *
backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs send *
backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs filesystem *
Important: Set correct permissions on the sudoers file (required by most distros):
sudo chmod 440 /etc/sudoers.d/btrfs-backup
Verify sudo works without password:
# Test on remote server (as backup user)
sudo -n btrfs --version
# Should print version without prompting for password
Step 3: Set Up SSH Key Authentication (Local Machine)
On your local machine, generate a dedicated SSH key for backups:
# Generate Ed25519 key (recommended)
ssh-keygen -t ed25519 -f ~/.ssh/btrfs-backup-key -C "btrfs-backup-ng"
# Or RSA if Ed25519 isn't supported
ssh-keygen -t rsa -b 4096 -f ~/.ssh/btrfs-backup-key -C "btrfs-backup-ng"
Copy the public key to the remote server:
# Copy key to remote backup user
ssh-copy-id -i ~/.ssh/btrfs-backup-key backup@remote-server
# Test the connection
ssh -i ~/.ssh/btrfs-backup-key backup@remote-server 'echo "SSH works!"'
Step 4: Test Remote btrfs Access
Verify everything works end-to-end:
# Test SSH + sudo + btrfs (from local machine)
ssh -i ~/.ssh/btrfs-backup-key backup@remote-server 'sudo btrfs filesystem show /mnt/backups'
If this command succeeds without prompting for any passwords, you're ready.
Step 5: Configure btrfs-backup-ng
Create your configuration file:
[global]
snapshot_dir = ".snapshots"
log_file = "/var/log/btrfs-backup-ng.log"
transaction_log = "/var/log/btrfs-backup-ng.jsonl"
[[volumes]]
path = "/home"
snapshot_prefix = "home"
[[volumes.targets]]
path = "ssh://backup@remote-server:/mnt/backups/home"
ssh_sudo = true # Required for btrfs receive
ssh_key = "~/.ssh/btrfs-backup-key" # Path to private key
Step 6: Running Backups with sudo Locally
btrfs operations on the local machine also require root. When running with sudo, you need to preserve your SSH agent:
# Method 1: Use sudo -E to preserve environment (including SSH_AUTH_SOCK)
sudo -E btrfs-backup-ng run
# Method 2: Explicitly specify the SSH key in config (works without agent)
# Just ensure ssh_key is set in your target configuration
sudo btrfs-backup-ng run
Important: When sudo changes to root, it normally loses access to your user's SSH agent. Options:
| Method | How | Best For |
|---|---|---|
sudo -E |
Preserves SSH_AUTH_SOCK | Interactive use |
ssh_key in config |
Uses key file directly | Automated/systemd |
| Root's SSH key | Generate key for root user | Dedicated backup systems |
Complete Working Example
Local machine setup:
# 1. Generate SSH key
ssh-keygen -t ed25519 -f ~/.ssh/btrfs-backup-key -N "" -C "btrfs-backup-ng"
# 2. Copy to remote
ssh-copy-id -i ~/.ssh/btrfs-backup-key backup@backupserver
# 3. Create config
sudo mkdir -p /etc/btrfs-backup-ng
sudo tee /etc/btrfs-backup-ng/config.toml << 'EOF'
[global]
snapshot_dir = ".snapshots"
[[volumes]]
path = "/home"
[[volumes.targets]]
path = "ssh://backup@backupserver:/mnt/backups/home"
ssh_sudo = true
ssh_key = "/home/myuser/.ssh/btrfs-backup-key"
EOF
# 4. Test
sudo btrfs-backup-ng run --dry-run
sudo btrfs-backup-ng run
Remote server setup:
# 1. Create backup user
sudo useradd -m backup
# 2. Configure passwordless sudo
echo 'backup ALL=(ALL) NOPASSWD: /usr/bin/btrfs' | sudo tee /etc/sudoers.d/btrfs-backup
sudo chmod 440 /etc/sudoers.d/btrfs-backup
# 3. Create backup directory (must be on btrfs)
sudo mkdir -p /mnt/backups/home
sudo chown backup:backup /mnt/backups/home
SSH Troubleshooting
"Permission denied" on SSH connect:
# Check key permissions (must be 600)
chmod 600 ~/.ssh/btrfs-backup-key
# Test SSH manually with verbose output
ssh -vvv -i ~/.ssh/btrfs-backup-key backup@remote-server
"sudo: a password is required":
# On remote, check sudoers syntax
sudo visudo -c -f /etc/sudoers.d/btrfs-backup
# Test sudo as backup user
sudo -u backup sudo -n btrfs --version
"ERROR: not a btrfs filesystem":
# On remote, verify backup location is btrfs
df -T /mnt/backups
# Must show "btrfs" as filesystem type
SSH works manually but fails in btrfs-backup-ng:
# Check if running with sudo drops SSH agent
sudo env | grep SSH_AUTH_SOCK # Should show your socket
# If empty, either use sudo -E or specify ssh_key in config
sudo -E btrfs-backup-ng run
Password-Based Authentication (Not Recommended)
If you cannot set up passwordless authentication, btrfs-backup-ng supports password prompts:
[[volumes.targets]]
path = "ssh://backup@remote-server:/mnt/backups"
ssh_sudo = true
ssh_password_auth = true # Enable SSH password prompts
For sudo passwords on remote:
# Set via environment variable (insecure - visible in process list)
export BTRFS_BACKUP_SUDO_PASSWORD="password"
sudo -E btrfs-backup-ng run
Warning: Password-based authentication is not suitable for automated/unattended backups.
Restoring from Backups
The restore command enables pulling snapshots from backup storage back to local systems. This is essential for disaster recovery, migration, and backup verification.
Basic Restore Operations
# List available snapshots at backup location
btrfs-backup-ng restore --list ssh://backup@server:/backups/home
btrfs-backup-ng restore --list /mnt/external-backup/home
# Restore latest snapshot
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore
# Restore specific snapshot by name
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore \
--snapshot home-20260104-120000
# Restore snapshot before a specific date
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore \
--before "2026-01-01 12:00"
# Interactive selection (shows list, lets you pick)
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore --interactive
# Restore ALL snapshots (full mirror)
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore --all
# Dry run (show what would be restored)
btrfs-backup-ng restore --dry-run ssh://backup@server:/backups/home /mnt/restore
How Restore Works
btrfs-backup-ng automatically handles incremental restore chains:
Backup has: [snap-1, snap-2, snap-3, snap-4]
↓ ↓ ↓ ↓
(full) (incr) (incr) (incr)
You request: snap-4
Restore chain: snap-1 → snap-2 → snap-3 → snap-4
(2.1 GB) (156 MB) (89 MB) (234 MB)
The restore command:
- Analyzes the parent chain required for the target snapshot
- Checks which parents already exist locally (can skip those)
- Restores snapshots in order (oldest first) to satisfy dependencies
- Uses incremental transfers when possible (much faster)
Restore Options
| Option | Description |
|---|---|
-l, --list |
List available snapshots at backup location |
-s, --snapshot NAME |
Restore specific snapshot by name |
--before DATETIME |
Restore snapshot closest to this time (YYYY-MM-DD [HH:MM:SS]) |
-a, --all |
Restore all snapshots (full mirror) |
-i, --interactive |
Interactive snapshot selection |
--dry-run |
Show what would be restored without making changes |
--no-incremental |
Force full transfers (skip incremental) |
--overwrite |
Overwrite existing snapshots instead of skipping |
--in-place |
Restore to original location (DANGEROUS, requires confirmation) |
--yes-i-know-what-i-am-doing |
Confirm dangerous operations like in-place restore |
--prefix PREFIX |
Snapshot prefix filter (include trailing hyphen, e.g., home-) |
--ssh-sudo |
Use sudo on remote for btrfs commands |
--ssh-key FILE |
SSH private key file for authentication |
--compress METHOD |
Compression for transfer (none, zstd, gzip, lz4, pigz, lzop) |
--rate-limit RATE |
Bandwidth limit (e.g., '10M', '1G') |
--no-fs-checks |
Skip btrfs subvolume verification (needed for backup directories) |
--progress |
Show progress bars (default in terminal) |
--no-progress |
Disable progress bars |
--status |
Show locks and incomplete restores at backup location |
--unlock [ID] |
Unlock stuck restore sessions ('all' or specific session ID) |
--cleanup |
Clean up partial/incomplete restores at destination |
Config-driven restore options:
| Option | Description |
|---|---|
-c, --config FILE |
Path to configuration file |
--volume PATH |
Restore backups for volume defined in config (e.g., /home) |
--target INDEX |
Target index to restore from (0-based, default: first target) |
--to PATH |
Destination path for config-driven restore |
--list-volumes |
List volumes and their backup targets from config |
Config-Driven Restore
Instead of specifying full paths, you can use your configuration file to restore from configured backup targets:
# List all configured volumes and their backup targets
btrfs-backup-ng restore --list-volumes
# List available snapshots for a configured volume
btrfs-backup-ng restore --volume /home --list
# Restore from configured backup target
btrfs-backup-ng restore --volume /home --to /mnt/restore
# Restore from second target (index 1)
btrfs-backup-ng restore --volume /home --target 1 --to /mnt/restore
# Interactive restore from config
btrfs-backup-ng restore --volume /home --to /mnt/restore --interactive
This is especially useful when you have complex SSH paths or multiple backup targets - the config file stores all the details.
Restore from Local Backup
# External drive backup
btrfs-backup-ng restore /mnt/external-backup/home /mnt/restore
# With specific snapshot
btrfs-backup-ng restore /mnt/external-backup/home /mnt/restore \
--snapshot home-20260104-120000
Restore from Remote Backup
# SSH backup server
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore \
--ssh-sudo \
--ssh-key ~/.ssh/backup_key
# With compression for slow links
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/restore \
--compress=zstd --rate-limit=10M
Disaster Recovery Walkthrough
Complete example of recovering a system from backup:
# 1. Boot from live USB/recovery environment
# 2. Mount your btrfs filesystem
mount /dev/sda2 /mnt/newroot
# 3. Create restore target directory
mkdir -p /mnt/newroot/restored-home
# 4. List available backups
btrfs-backup-ng restore --list ssh://backup@server:/backups/home
# 5. Restore the latest snapshot
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/newroot/restored-home \
--ssh-sudo
# 6. (Optional) Restore to specific point in time
btrfs-backup-ng restore ssh://backup@server:/backups/home /mnt/newroot/restored-home \
--before "2026-01-01" --ssh-sudo
# 7. Verify the restore
ls -la /mnt/newroot/restored-home/
# 8. Rename/move the restored snapshot to final location
# (depends on your specific setup)
Restore Strategies: Choosing the Right Approach
There are several ways to restore data from backups, each with different trade-offs. Choose the approach that best fits your situation and comfort level.
Strategy 1: Restore to Temporary Location (Recommended)
Best for: Most recovery scenarios, file recovery, verification before committing
This is the safest approach - restore to a separate location, verify the contents, then manually move or copy what you need.
# 1. Create a temporary restore directory (must be on btrfs)
sudo mkdir -p /mnt/btrfs-restore
# 2. Restore the snapshot you want
sudo btrfs-backup-ng restore /mnt/backup/home /mnt/btrfs-restore \
--snapshot home-20260104-120000 \
--prefix "home-" \
--no-fs-checks
# 3. Verify the restored snapshot contains what you expect
ls -la /mnt/btrfs-restore/home-20260104-120000/
diff -r /mnt/btrfs-restore/home-20260104-120000/Documents ~/Documents
# 4a. Copy specific files you need (safest)
cp -a /mnt/btrfs-restore/home-20260104-120000/Documents/important.doc ~/Documents/
# 4b. Or replace entire directory (after backing up current)
mv ~/Documents ~/Documents.old
cp -a /mnt/btrfs-restore/home-20260104-120000/Documents ~/Documents
# 5. Clean up when done
sudo btrfs subvolume delete /mnt/btrfs-restore/home-20260104-120000
Pros:
- Completely safe - original data untouched until you're ready
- Can inspect and verify before committing
- Can selectively restore individual files or directories
- Easy to abort if something is wrong
Cons:
- Requires extra disk space for temporary restore
- More manual steps involved
- Slower for full system recovery
Strategy 2: Restore and Rename with Btrfs Snapshots
Best for: Full directory replacement while keeping a safety net
Use btrfs's native snapshot capability to create a backup of current state before replacing.
# 1. Create a snapshot of your CURRENT state (safety net)
sudo btrfs subvolume snapshot /home /home.before-restore
# 2. Restore the backup snapshot to a temporary location
sudo btrfs-backup-ng restore /mnt/backup/home /mnt/btrfs-restore \
--snapshot home-20260104-120000 \
--prefix "home-" \
--no-fs-checks
# 3. Verify the restore looks correct
ls -la /mnt/btrfs-restore/home-20260104-120000/
# 4. Rename current to old, restored to current
sudo mv /home /home.old
sudo mv /mnt/btrfs-restore/home-20260104-120000 /home
# 5. If everything works, clean up old versions later
sudo btrfs subvolume delete /home.old
sudo btrfs subvolume delete /home.before-restore
# 5b. If something went wrong, roll back:
sudo mv /home /home.failed-restore
sudo mv /home.before-restore /home
Pros:
- Full replacement with automatic rollback option
- Uses btrfs efficiency (snapshots are instant, space-efficient)
- Clear before/after comparison possible
Cons:
- Requires understanding of btrfs subvolume management
- Need to handle mount points if /home is a separate subvolume
- May require reboot or remount for mount point changes
Strategy 3: Manual Btrfs Send/Receive
Best for: Advanced users, scripted recovery, maximum control
Use raw btrfs commands for complete control over the restore process.
# 1. From local backup drive
sudo btrfs send /mnt/backup/home/home-20260104-120000 | sudo btrfs receive /mnt/restore/
# 2. From remote backup via SSH
ssh backup@server "sudo btrfs send /backups/home/home-20260104-120000" | \
sudo btrfs receive /mnt/restore/
# 3. With compression for slow networks
ssh backup@server "sudo btrfs send /backups/home/home-20260104-120000 | zstd" | \
zstd -d | sudo btrfs receive /mnt/restore/
# 4. Incremental restore (if you have the parent locally)
ssh backup@server "sudo btrfs send -p /backups/home/home-20260103-120000 \
/backups/home/home-20260104-120000" | sudo btrfs receive /mnt/restore/
Pros:
- Maximum flexibility and control
- Can be easily scripted
- Works in minimal recovery environments
- No dependency on btrfs-backup-ng being installed
Cons:
- Must manually handle incremental parent chains
- No progress display or error recovery
- Easy to make mistakes with complex commands
- Must manually manage snapshot prefixes and naming
Strategy 4: In-Place Restore (Use with Caution)
Best for: Disaster recovery when you're certain, automated recovery scripts
Direct replacement of the target location. This is the most dangerous but fastest approach.
# WARNING: This will OVERWRITE existing data at /home
# Make absolutely sure you have the right snapshot!
# 1. First, verify what you're about to restore
btrfs-backup-ng restore --list ssh://backup@server:/backups/home \
--prefix "home-" --no-fs-checks
# 2. Do a dry-run first
btrfs-backup-ng restore ssh://backup@server:/backups/home /home \
--in-place \
--snapshot home-20260104-120000 \
--prefix "home-" \
--dry-run
# 3. If dry-run looks correct, proceed with actual restore
btrfs-backup-ng restore ssh://backup@server:/backups/home /home \
--in-place \
--yes-i-know-what-i-am-doing \
--snapshot home-20260104-120000 \
--prefix "home-" \
--ssh-sudo
Pros:
- Fastest for full recovery
- Single command, minimal steps
- Handles incremental chains automatically
Cons:
- DESTRUCTIVE - existing data is overwritten
- No easy rollback if wrong snapshot chosen
- Requires explicit confirmation flag
- Not suitable for partial recovery
Strategy Comparison
| Strategy | Safety | Speed | Disk Space | Complexity | Best For |
|---|---|---|---|---|---|
| Temporary location | Highest | Slow | Requires extra | Low | File recovery, verification |
| Rename with snapshots | High | Medium | Minimal extra | Medium | Full replacement with rollback |
| Manual btrfs commands | Medium | Fast | Minimal | High | Advanced users, scripting |
| In-place restore | Lowest | Fastest | None | Low | Disaster recovery, automation |
Recommendations by Scenario
"I accidentally deleted some files": → Use Strategy 1 (temporary location), copy just the files you need
"My system is corrupted, I need to restore /home": → Use Strategy 2 (rename with snapshots) for safety, or Strategy 4 if you're confident
"I'm setting up a new machine from backups": → Use Strategy 1 or the restore command directly - there's nothing to lose
"I'm writing an automated disaster recovery script": → Use Strategy 3 (manual commands) or Strategy 4 (in-place) depending on your safety requirements
"I'm in a minimal recovery environment without btrfs-backup-ng": → Use Strategy 3 (manual btrfs send/receive)
Collision Handling
By default, snapshots that already exist locally are skipped:
# Skip existing (default)
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore
# Overwrite existing snapshots
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore --overwrite
Recovery from Failed Restores
If a restore operation is interrupted or fails, use these recovery commands:
Check Status of Locks
# See what locks exist at the backup location
btrfs-backup-ng restore --status /mnt/backup/home --no-fs-checks --prefix "home-"
# For SSH backups
btrfs-backup-ng restore --status ssh://backup@server:/backups/home \
--no-fs-checks --prefix "home-" --ssh-sudo
Output shows:
- Restore locks - From incomplete restore operations
- Other locks - From backup/transfer operations (not touched by unlock)
- Number of available snapshots
Unlock Stuck Sessions
If a restore was interrupted, locks may remain on snapshots preventing future operations:
# Unlock ALL restore sessions (recommended)
btrfs-backup-ng restore --unlock all /mnt/backup/home --no-fs-checks
# Unlock a specific session by ID
btrfs-backup-ng restore --unlock abc123 /mnt/backup/home --no-fs-checks
# For SSH backups
btrfs-backup-ng restore --unlock all ssh://backup@server:/backups/home \
--no-fs-checks --ssh-sudo
Note: --unlock only removes restore locks. Backup and transfer locks are preserved to prevent accidental data loss.
Clean Up Partial Restores
If a restore failed mid-transfer, partial subvolumes may remain at the destination:
# Scan for partial restores (dry-run first)
btrfs-backup-ng restore --cleanup /mnt/restore --dry-run
# Actually clean up partial subvolumes (requires confirmation)
btrfs-backup-ng restore --cleanup /mnt/restore
The cleanup command detects:
- Empty subvolumes
- Subvolumes containing only metadata directories
- Subvolumes with
.partialsuffix
Complete Recovery Example
# 1. Check what's stuck
btrfs-backup-ng restore --status /mnt/backup/home --no-fs-checks --prefix "home-"
# 2. Unlock any stuck restore sessions
btrfs-backup-ng restore --unlock all /mnt/backup/home --no-fs-checks
# 3. Clean up any partial restores at destination
btrfs-backup-ng restore --cleanup /mnt/restore
# 4. Retry the restore
btrfs-backup-ng restore /mnt/backup/home /mnt/restore \
--prefix "home-" --no-fs-checks
Troubleshooting Restore
"Source does not seem to be a btrfs subvolume":
# Backup directories are regular directories containing snapshot subvolumes
# Use --no-fs-checks to skip subvolume verification when listing/restoring
btrfs-backup-ng restore --list /mnt/backup/home --no-fs-checks --prefix "home-"
# This is normal - the backup location is a directory, not a subvolume itself
"Destination is not on a btrfs filesystem":
# The restore target must be on btrfs
df -T /mnt/restore | grep btrfs
# If not btrfs, create or mount a btrfs filesystem first
"No snapshots found at backup location":
# List available snapshots to see what's there
btrfs-backup-ng restore --list ssh://backup@server:/backups/home --no-fs-checks
# IMPORTANT: The prefix must include the trailing hyphen!
# If snapshots are named "home-20260104-120000", use --prefix "home-"
btrfs-backup-ng restore --list /mnt/backup --no-fs-checks --prefix "home-"
# Without the correct prefix, date parsing will fail and snapshots won't be found
"Could not parse date from snapshot name":
# This usually means the prefix doesn't match or is missing the trailing hyphen
# Snapshot name format: {prefix}{YYYYMMDD-HHMMSS}
# Example: home-20260104-120000 requires --prefix "home-"
# Check your snapshot names first
ls /mnt/backup/home/
# Output: home-20260104-120000 home-20260104-140000 ...
# Then use the correct prefix (including the hyphen)
btrfs-backup-ng restore --list /mnt/backup/home --prefix "home-" --no-fs-checks
"Permission denied" during restore:
# For SSH backups, ensure ssh_sudo is enabled
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore --ssh-sudo
# Local restore requires root for btrfs receive
sudo btrfs-backup-ng restore /mnt/backup /mnt/restore --no-fs-checks
"btrfs send/receive failed" when snapshot already exists:
# By default, existing snapshots are skipped but may show errors
# Use --overwrite to replace existing snapshots
btrfs-backup-ng restore /mnt/backup /mnt/restore --overwrite --no-fs-checks
# Or restore to a clean directory
mkdir /mnt/restore-new
btrfs-backup-ng restore /mnt/backup /mnt/restore-new --no-fs-checks
Transfer is very slow:
# Use compression for remote restores
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore --compress=zstd
# Check if incremental is working (should show "incremental from X")
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore -v
# Limit bandwidth if needed
btrfs-backup-ng restore ssh://...:/backups/home /mnt/restore --rate-limit=50M
Important Notes
-
The
--no-fs-checksflag is usually required when listing or restoring from backup directories. Backup locations are regular directories containing snapshot subvolumes, not subvolumes themselves. -
The
--prefixmust include the trailing hyphen that separates the prefix from the timestamp. If your snapshots are namedhome-20260104-120000, use--prefix "home-"not--prefix "home". -
Restore destination must be on btrfs. The
btrfs receivecommand requires a btrfs filesystem to create subvolumes. -
Incremental restore chains are automatic. When restoring a snapshot that depends on parent snapshots, all required parents are restored first in the correct order.
-
Root privileges are typically required for
btrfs receive. Usesudofor local restores or--ssh-sudofor remote restores.
Backup Verification
Verify that your backups are valid and restorable with the verify command. A backup you can't restore from is worthless - regular verification ensures your disaster recovery will work when you need it.
Verification Levels
| Level | Speed | What It Checks |
|---|---|---|
metadata |
Fast | Snapshot existence, parent chain completeness |
stream |
Medium | btrfs send stream can be generated (no data transfer) |
full |
Slow | Actually restores to temp location and verifies |
Quick Metadata Check
# Verify local backup
btrfs-backup-ng verify /mnt/backup/home --no-fs-checks
# Verify remote backup over SSH
btrfs-backup-ng verify ssh://backup@server:/backups/home --ssh-sudo --no-fs-checks
Stream Verification
Tests that btrfs send stream can be generated without transferring data:
btrfs-backup-ng verify /mnt/backup/home --level stream --no-fs-checks
Full Restore Test
The definitive test - actually restores snapshot to a temporary location:
# For local backups (temp dir created automatically)
btrfs-backup-ng verify /mnt/backup/home --level full --no-fs-checks
# For remote backups (must specify local btrfs temp dir)
btrfs-backup-ng verify ssh://server:/backups/home --level full \
--temp-dir /mnt/btrfs-test --ssh-sudo --no-fs-checks
Verify Specific Snapshot
btrfs-backup-ng verify /mnt/backup/home --snapshot home-20260104-120000 --no-fs-checks
Automation with JSON Output
# Get machine-readable output
btrfs-backup-ng verify /mnt/backup/home --json --no-fs-checks
# Example: Alert on failure
if ! btrfs-backup-ng verify /mnt/backup/home --quiet --no-fs-checks; then
echo "Backup verification failed!" | mail -s "Backup Alert" admin@example.com
fi
Verification Options
| Option | Description |
|---|---|
--level |
Verification level: metadata, stream, or full |
--snapshot NAME |
Verify only specific snapshot |
--temp-dir PATH |
Temp directory for full verification (must be on btrfs) |
--no-cleanup |
Keep restored snapshots after full verification |
--prefix PREFIX |
Filter snapshots by prefix |
--ssh-sudo |
Use sudo for remote btrfs commands |
--ssh-key FILE |
SSH private key file |
--no-fs-checks |
Skip btrfs subvolume verification |
--json |
Output in JSON format |
-q, --quiet |
Suppress progress output |
Backup Size Estimation
Before running backups, you can estimate how much data will be transferred using the estimate command. This helps with:
- Planning bandwidth usage and backup windows
- Verifying expected transfer sizes
- Scripting and automation decisions
Basic Estimation
# Estimate transfer size for direct paths
btrfs-backup-ng estimate /mnt/snapshots /mnt/backup
# Estimate for remote backup
btrfs-backup-ng estimate /home/.snapshots ssh://backup@server:/backups/home
Config-Driven Estimation
# List configured volumes and their targets
btrfs-backup-ng estimate --list-volumes
# Estimate for a specific volume from config
btrfs-backup-ng estimate --volume /home
# Estimate for a specific target (0-based index)
btrfs-backup-ng estimate --volume /home --target 1
JSON Output for Scripting
# Get machine-readable output
btrfs-backup-ng estimate --volume /home --json
# Example: Check if backup will fit
SIZE=$(btrfs-backup-ng estimate --volume /home --json | jq '.total_transfer_bytes')
AVAILABLE=$(df --output=avail -B1 /mnt/backup | tail -1)
if [ "$SIZE" -gt "$AVAILABLE" ]; then
echo "Warning: Not enough space for backup"
fi
Example Output
Backup Size Estimate
============================================================
Source: /home/.snapshots
Destination: ssh://backup@server:/backups/home
Snapshots already at destination: 45
Snapshots to transfer: 3
Snapshot Size Type Parent
---------------------------------------- ------------ ------------ --------------------
home-20260104-080000 156.23 MiB incremental home-20260103-200000
home-20260104-120000 89.45 MiB incremental home-20260104-080000
home-20260104-160000 234.12 MiB incremental home-20260104-120000
------------------------------------------------------------
Total data to transfer: 479.80 MiB
Full size (uncompressed): 2.34 GiB
Estimation time: 1.23s
Estimation Options
| Option | Description |
|---|---|
--volume PATH |
Estimate for volume defined in config |
--target INDEX |
Target index to estimate for (0-based) |
-c, --config FILE |
Path to configuration file |
--prefix PREFIX |
Snapshot prefix filter |
--ssh-sudo |
Use sudo on remote host |
--ssh-key FILE |
SSH private key file |
--json |
Output in JSON format |
Estimation Methods
The estimate command uses multiple methods to determine sizes:
- btrfs subvolume show - Uses exclusive data size (most accurate, requires quotas)
- btrfs filesystem du - Handles reflinks and deduplication correctly
- du - Fallback for when btrfs commands aren't available
For incremental transfers, the command uses btrfs send --no-data to estimate the delta size without actually transferring data.
Legacy CLI Mode
The original command-line interface still works:
# Basic backup
btrfs-backup-ng /source/subvolume /destination/path
# Remote backup
btrfs-backup-ng /home ssh://backup@server:/backups/home
# With options
btrfs-backup-ng --ssh-sudo --num-snapshots 10 /source ssh://user@host:/dest
Legacy mode is auto-detected when the first argument is a path.
Systemd Integration
btrfs-backup-ng can install systemd timer and service units for automated backups. There are two installation modes:
| Mode | Install Location | Runs As | Use Case |
|---|---|---|---|
| System-wide | /etc/systemd/system/ |
root | Servers, production systems |
| User-level | ~/.config/systemd/user/ |
current user | Personal workstations |
System-Wide Installation (Recommended for Servers)
System-wide installation runs backups as root, which is required for backing up system volumes.
Prerequisites:
- Configuration file at
/etc/btrfs-backup-ng/config.toml(or user config) - Root privileges (sudo)
Installation:
# Install with a preset timer
sudo btrfs-backup-ng install --timer=hourly # Every hour at :00
sudo btrfs-backup-ng install --timer=daily # Daily at 02:00
sudo btrfs-backup-ng install --timer=weekly # Weekly on Sunday at 02:00
# Or use a custom OnCalendar specification
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
Enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now btrfs-backup-ng.timer
Generated files:
/etc/systemd/system/btrfs-backup-ng.service- The backup service unit/etc/systemd/system/btrfs-backup-ng.timer- The timer unit
User-Level Installation
User-level installation runs backups as the current user. Useful for personal workstations where sudo is available but system-wide installation isn't desired.
Note: User-level services require the user to be logged in (or lingering enabled).
# Install user timer
btrfs-backup-ng install --user --timer=hourly
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now btrfs-backup-ng.timer
# Optional: Enable lingering so timers run even when logged out
loginctl enable-linger $USER
Generated files:
~/.config/systemd/user/btrfs-backup-ng.service~/.config/systemd/user/btrfs-backup-ng.timer
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 |
Custom OnCalendar Examples
The --oncalendar option accepts any valid systemd calendar specification:
| Specification | Description |
|---|---|
*:0/5 |
Every 5 minutes |
*:0/15 |
Every 15 minutes |
*:0/30 |
Every 30 minutes |
hourly |
Every hour |
*-*-* 02:00:00 |
Daily at 2:00 AM |
*-*-* 00/6:00:00 |
Every 6 hours |
Mon,Thu *-*-* 03:00:00 |
Monday and Thursday at 3:00 AM |
*-*-01 04:00:00 |
First day of each month at 4:00 AM |
Test your specification with: systemd-analyze calendar '*:0/15'
Monitoring and Logs
Check timer status:
# System-wide
systemctl status btrfs-backup-ng.timer
systemctl list-timers btrfs-backup-ng.timer
# User-level
systemctl --user status btrfs-backup-ng.timer
systemctl --user list-timers btrfs-backup-ng.timer
View logs:
# System-wide - recent logs
journalctl -u btrfs-backup-ng.service -n 50
# System-wide - follow logs in real-time
journalctl -u btrfs-backup-ng.service -f
# System-wide - logs since last boot
journalctl -u btrfs-backup-ng.service -b
# User-level
journalctl --user -u btrfs-backup-ng.service -n 50
Manually trigger a backup:
# System-wide
sudo systemctl start btrfs-backup-ng.service
# User-level
systemctl --user start btrfs-backup-ng.service
Uninstall
The uninstall command removes the timer and service files:
# System-wide
sudo btrfs-backup-ng uninstall
# User-level
btrfs-backup-ng uninstall
Note: Uninstall checks both system and user locations and removes files from wherever they're found.
Complete System-Wide Setup Example
# 1. Create and configure (using interactive wizard)
sudo mkdir -p /etc/btrfs-backup-ng
sudo btrfs-backup-ng config init -i -o /etc/btrfs-backup-ng/config.toml
sudo chmod 600 /etc/btrfs-backup-ng/config.toml
# Or manually: btrfs-backup-ng config init | sudo tee /etc/btrfs-backup-ng/config.toml
# sudo $EDITOR /etc/btrfs-backup-ng/config.toml
# 2. Validate and test
sudo btrfs-backup-ng config validate
sudo btrfs-backup-ng run --dry-run
sudo btrfs-backup-ng run
# 3. Install timer (hourly backups)
sudo btrfs-backup-ng install --timer=hourly
# 4. Enable timer
sudo systemctl daemon-reload
sudo systemctl enable --now btrfs-backup-ng.timer
# 5. Verify
systemctl list-timers btrfs-backup-ng.timer
sudo btrfs-backup-ng status
Troubleshooting
Debug Logging
btrfs-backup-ng -v run # Verbose
btrfs-backup-ng -vv run # Debug level
Common Issues
Permission denied on remote:
# Check sudo configuration
ssh user@remote 'sudo -n btrfs --version'
# Enable ssh_sudo in config
[[volumes.targets]]
path = "ssh://user@remote:/backup"
ssh_sudo = true
Snapshot directory doesn't exist:
# btrfs-backup-ng creates it automatically, but ensure parent exists
mkdir -p /path/to/.snapshots
Transfer fails with compression:
# Verify compression tool is installed on both ends
which zstd
ssh user@remote 'which zstd'
Rate limiting not working:
# Install pv (pipe viewer)
sudo dnf install pv
Validate Configuration
btrfs-backup-ng config validate
Development
Running from Source
git clone https://github.com/berrym/btrfs-backup-ng.git
cd btrfs-backup-ng
pip install -e .
python -m btrfs_backup_ng --help
Project Structure
src/btrfs_backup_ng/
__main__.py # Entry point
config/ # TOML configuration system
schema.py # Config dataclasses
loader.py # TOML loading and validation
cli/ # Subcommand modules
dispatcher.py # Command routing
run.py, snapshot.py, transfer.py, prune.py, ...
core/ # Core operations
operations.py # send_snapshot, sync_snapshots
transfer.py # Compression, throttling
planning.py # Transfer planning
endpoint/ # Endpoint implementations
local.py # Local filesystem
ssh.py # SSH remote
shell.py # Shell commands
retention.py # Time-based retention logic
btrbk_import.py # btrbk config importer
Documentation
- CLI Reference - Complete command and option reference
- Migrating from btrbk - Guide for btrbk users
- Changelog - Version history and release notes
Example Configurations
Ready-to-use configuration examples in examples/:
| File | Description |
|---|---|
minimal.toml |
Simple local backup |
remote-backup.toml |
Multi-volume SSH backup |
server.toml |
Server with frequent snapshots |
config.toml |
Full reference with all options |
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please:
- Follow PEP 8 style guidelines
- Add tests for new features
- Update documentation as needed
- Maintain backward compatibility
See Also
- btrbk - The Perl-based tool that inspired many features
- btrfs-progs - BTRFS utilities
- Snapper - Snapshot management tool
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file btrfs_backup_ng-0.8.0.tar.gz.
File metadata
- Download URL: btrfs_backup_ng-0.8.0.tar.gz
- Upload date:
- Size: 212.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75367cd7a1d20e51464d81fbefe40120c04ce0853841ff9592928e1b1b2f1799
|
|
| MD5 |
917e5147ec60a38bd059418a12412096
|
|
| BLAKE2b-256 |
e733c40733c9e44d325c96c04db6456d9705711151e70350f3a73533ae564e54
|
Provenance
The following attestation bundles were made for btrfs_backup_ng-0.8.0.tar.gz:
Publisher:
release.yml on berrym/btrfs-backup-ng
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
btrfs_backup_ng-0.8.0.tar.gz -
Subject digest:
75367cd7a1d20e51464d81fbefe40120c04ce0853841ff9592928e1b1b2f1799 - Sigstore transparency entry: 790911879
- Sigstore integration time:
-
Permalink:
berrym/btrfs-backup-ng@bf0dd64864a31d680d7575fb492a33c1d18fb8b0 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/berrym
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bf0dd64864a31d680d7575fb492a33c1d18fb8b0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file btrfs_backup_ng-0.8.0-py3-none-any.whl.
File metadata
- Download URL: btrfs_backup_ng-0.8.0-py3-none-any.whl
- Upload date:
- Size: 174.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14cf8231b32567024728c61047a891ca76c0c25ab077c9666fb11168eea56d85
|
|
| MD5 |
3d3eb40040d7dce1ff59568922640ad9
|
|
| BLAKE2b-256 |
b4e8816a57fd2f5dff3139f543934e08c4805b1de217f3e938078584b06416ab
|
Provenance
The following attestation bundles were made for btrfs_backup_ng-0.8.0-py3-none-any.whl:
Publisher:
release.yml on berrym/btrfs-backup-ng
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
btrfs_backup_ng-0.8.0-py3-none-any.whl -
Subject digest:
14cf8231b32567024728c61047a891ca76c0c25ab077c9666fb11168eea56d85 - Sigstore transparency entry: 790911880
- Sigstore integration time:
-
Permalink:
berrym/btrfs-backup-ng@bf0dd64864a31d680d7575fb492a33c1d18fb8b0 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/berrym
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bf0dd64864a31d680d7575fb492a33c1d18fb8b0 -
Trigger Event:
release
-
Statement type: