This cheatsheet covers three modern backup tools — rsync, restic, and borg — along with general strategies for designing resilient backup systems.
Rsync
Basic Patterns
# Local copy of directory
rsync -av /source/ /destination/
# Remote copy (push to remote)
rsync -av /local/path/ user@remote:/remote/path/
# Remote copy (pull from remote)
rsync -av user@remote:/remote/path/ /local/path/
# Archive mode (preserves permissions, timestamps, symlinks, etc.)
rsync -a /source/ /destination/
# Verbose + progress + human-readable
rsync -avh --progress /source/ /destination/
# Compress during transfer
rsync -azv /source/ user@remote:/destination/
# Dry-run (show what would be transferred, no changes)
rsync -av --dry-run /source/ /destination/
# Archive with compression and delete
rsync -azv --delete /source/ user@remote:/destination/Include/Exclude Patterns
# Exclude specific files/directories
rsync -av --exclude '*.tmp' /source/ /destination/
rsync -av --exclude 'node_modules' /source/ /destination/
rsync -av --exclude '.git' --exclude '.cache' /source/ /destination/
# Exclude from file list
rsync -av --exclude-from=/path/to/exclude-list.txt /source/ /destination/
# Include only specific patterns
rsync -av --include '*.pdf' --include '*.doc' --exclude '*' /source/ /destination/
# Include-only with directory structure
rsync -av --include '*/' --include '*.jpg' --exclude '*' /photos/ /backup/
# Exclude common development artifacts
cat > /etc/rsync-exclude.txt << 'EOF'
.git/
.gitignore
node_modules/
.cache/
*.log
*.pid
.DS_Store
Thumbs.db
.gitkeep
EOF
rsync -av --exclude-from=/etc/rsync-exclude.txt /source/ /destination/Incremental with --link-dest
# First full backup
rsync -avh --delete /source/ /backup/current/
# Create hard-link based incremental backup
# Files unchanged since last backup are hardlinked (saves space)
rsync -avh --delete --link-dest=/backup/current/ \
/source/ /backup/backup-$(date +%Y%m%d)/
# After the incremental, update "current" (for next iteration)
rsync -avh --delete /source/ /backup/current/
# Script for daily incremental backups
cat > /usr/local/bin/rsync-incremental.sh << 'SCRIPT'
#!/bin/bash
SOURCE="/home/user"
BACKUP_DIR="/backup"
CURRENT="$BACKUP_DIR/current"
SNAPSHOT="$BACKUP_DIR/snapshots/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR/snapshots"
rsync -avh --delete \
--link-dest="$CURRENT" \
--exclude=".cache" \
"$SOURCE/" "$SNAPSHOT/"
rm -f "$CURRENT"
ln -s "$SNAPSHOT" "$CURRENT"
SCRIPT
# Restore from incremental backup
# All hardlinked files are real files — just copy them
cp -a /backup/snapshots/20240101-120000/ /restore/location/Bandwidth Limiting & Partial Transfers
# Limit bandwidth to 1 MB/s (1000 KB/s)
rsync -av --bwlimit=1000 /source/ user@remote:/destination/
# Limit to 500 KB/s
rsync -av --bwlimit=500 /source/ user@remote:/destination/
# Partial transfer (resume interrupted transfers)
rsync -av --partial /source/ /destination/
# Uses .filename.random suffix for partial files
# Partial-dir (keep partials in a separate directory)
rsync -av --partial-dir=.rsync-partial /source/ /destination/
# Timeout settings for unreliable connections
rsync -av --timeout=30 --contimeout=30 /source/ remote:/dest/Over SSH
# Default SSH (port 22)
rsync -av -e ssh /source/ user@remote:/dest/
# Custom SSH port
rsync -av -e "ssh -p 2222" /source/ user@remote:/dest/
# SSH key authentication
rsync -av -e "ssh -i ~/.ssh/backup_key" /source/ user@remote:/dest/
# Compression + custom SSH options
rsync -azv -e "ssh -p 2222 -o Compression=no" /source/ remote:/dest/
# Jump host / bastion
rsync -av -e "ssh -J bastion.example.com" /source/ remote:/dest/
# SSH config for complex setups
# ~/.ssh/config:
# Host backup-server
# HostName 192.168.1.100
# Port 2222
# User backup
# IdentityFile ~/.ssh/backup_key
rsync -av /source/ backup-server:/dest/Rsync Daemon
# Server-side configuration: /etc/rsyncd.conf
cat > /etc/rsyncd.conf << 'CONF'
uid = root
gid = root
use chroot = yes
max connections = 4
syslog facility = local5
pid file = /run/rsyncd.pid
[backup]
path = /srv/backup
comment = Backup storage
read only = no
auth users = backupuser
secrets file = /etc/rsyncd.secrets
hosts allow = 192.168.1.0/24
hosts deny = *
CONF
# Secrets file: /etc/rsyncd.secrets
# backupuser:SuperSecretPassword
chmod 600 /etc/rsyncd.secrets
# Start the rsync daemon
systemctl enable rsync --now
# Client-side: connect to rsync daemon
rsync -av /source/ backupuser@server::backup/
# Using rsync:// URL
rsync -av /source/ rsync://backupuser@server:873/backup/
# List available modules
rsync backupuser@server::Restic
Repository Initialization
# Install restic
apt install -y restic
# Local repository
restic init --repo /mnt/backup/restic-repo
# SFTP repository (remote server)
restic init --repo sftp:user@backup-server:/srv/restic-repo
# S3-compatible storage (MinIO, AWS S3, etc.)
export AWS_ACCESS_KEY_ID=accesskey
export AWS_SECRET_ACCESS_KEY=secretkey
restic init --repo s3:s3.amazonaws.com/bucket-name
# S3 with custom endpoint (MinIO, R2, Backblaze B2 compatible)
restic init --repo s3:http://192.168.1.100:9000/bucket-name
# Backblaze B2
restic -r b2:bucket-name:path/to/repo init
# Rclone (supporting 40+ backends)
restic init --repo rclone:remote:path
# rest-server (self-hosted REST server)
restic init --repo rest:http://backup-server:8000/
# Set password
# Restic prompts for password interactively
# Or use environment variable: export RESTIC_PASSWORD=my-secret-passwordBackup Operations
# Basic backup
restic --repo /mnt/backup/restic-repo backup /home/user/
# Backup with specific repo password
restic -r /mnt/backup/restic-repo -p /etc/restic-password backup /home/user/
# Backup multiple directories
restic -r /mnt/backup/restic-repo backup /home/user /etc /var/www
# Exclude patterns
restic -r /mnt/backup/restic-repo backup /home/user/ \
--exclude="*.tmp" \
--exclude=".cache" \
--exclude="node_modules"
# Exclude from file
restic -r /mnt/backup/restic-repo backup /home/user/ \
--exclude-file=/etc/restic-excludes.txt
# Add tags to backup (for organization)
restic -r /mnt/backup/restic-repo backup /home/user/ \
--tag daily --tag server1
# Backup with verbose output
restic -r /mnt/backup/restic-repo backup -v /home/user/
# Backup specific files matching pattern
restic -r /mnt/backup/restic-repo backup /home/user/ \
--include="/home/user/Documents/*.pdf"
# Backup with priority (ionice + nice)
ionice -c 2 -n 7 nice -n 19 restic -r /mnt/backup/restic-repo backup /home/user/
# Database backup example (pre-backup dump)
pg_dump dbname > /tmp/db-dump.sql
restic -r /mnt/backup/restic-repo backup /tmp/db-dump.sql --tag database
rm /tmp/db-dump.sqlRestore Operations
# Restore entire snapshot to directory
restic -r /mnt/backup/restic-repo restore latest --target /restore/location/
# Restore specific snapshot by ID
restic -r /mnt/backup/restic-repo restore abc123def456 --target /restore/location/
# Restore specific path from snapshot
restic -r /mnt/backup/restic-repo restore latest \
--target /restore/location/ \
--path /home/user/Documents
# Restore to different path (path mapping)
restic -r /mnt/backup/restic-repo restore latest \
--target /tmp/restore \
--include /home/user/Documents/important.pdf
# Dry-run restore (show what would be restored)
restic -r /mnt/backup/restic-repo restore latest \
--target /restore/location/ \
--dry-run
# Verify restored data integrity
restic -r /mnt/backup/restic-repo restore latest \
--target /restore/location/ \
--verifyMount & Dump
# Mount repository as FUSE filesystem (browse all snapshots)
restic -r /mnt/backup/restic-repo mount /mnt/restic-mount
# Browse specific snapshot
cd /mnt/restic-mount
ls # Shows snapshot directories (IDs and timestamps)
cd latest # Access latest snapshot
cd abc123def # Access specific snapshot
# Unmount when done
fusermount -u /mnt/restic-mount
# Dump single file to stdout (no restore needed)
restic -r /mnt/backup/restic-repo dump latest /home/user/Documents/notes.txt
# Dump to file
restic -r /mnt/backup/restic-repo dump latest /home/user/Documents/notes.txt > /tmp/notes.txt
# Dump from specific snapshot
restic -r /mnt/backup/restic-repo dump abc123def /home/user/Documents/notes.txt > notes.txt
# Dump directory as tar
restic -r /mnt/backup/restic-repo dump latest /home/user/Documents/ | tar -tSnapshots Management
# List all snapshots
restic -r /mnt/backup/restic-repo snapshots
# List snapshots with tags
restic -r /mnt/backup/restic-repo snapshots --tag daily
# List snapshots compact format
restic -r /mnt/backup/restic-repo snapshots --compact
# Show snapshot details
restic -r /mnt/backup/restic-repo stats latest
restic -r /mnt/backup/restic-repo stats abc123def
# Show total repository stats
restic -r /mnt/backup/restic-repo stats
# Delete a snapshot
restic -r /mnt/backup/restic-repo forget abc123def
# Forget (remove) snapshots by policy
restic -r /mnt/backup/restic-repo forget \
--keep-last 7 \
--keep-daily 30 \
--keep-weekly 12 \
--keep-monthly 6 \
--keep-yearly 2
# Forget with prune (frees actual storage space)
restic -r /mnt/backup/restic-repo forget \
--keep-last 7 --keep-daily 30 --keep-weekly 12 \
--prune
# Prune orphaned data (after forget)
restic -r /mnt/backup/restic-repo prune
# Check repository integrity
restic -r /mnt/backup/restic-repo check
# Check and read all data
restic -r /mnt/backup/restic-repo check --read-data
# Check specific percentage of data
restic -r /mnt/backup/restic-repo check --read-data-subset=10%Automation
# Systemd service: /etc/systemd/system/restic-backup.service
cat > /etc/systemd/system/restic-backup.service << 'SERVICE'
[Unit]
Description=Restic backup service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh
Environment=RESTIC_PASSWORD=my-secret-password
Environment=AWS_ACCESS_KEY_ID=mykey
Environment=AWS_SECRET_ACCESS_KEY=mysecret
Nice=19
IOSchedulingClass=best-effort
IOSchedulingPriority=7
SERVICE
# Systemd timer: /etc/systemd/system/restic-backup.timer
cat > /etc/systemd/system/restic-backup.timer << 'TIMER'
[Unit]
Description=Daily restic backup
Requires=restic-backup.service
[Timer]
OnCalendar=daily
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
TIMER
# Enable timer
systemctl daemon-reload
systemctl enable --now restic-backup.timer
# Backup script: /usr/local/bin/restic-backup.sh
cat > /usr/local/bin/restic-backup.sh << 'SCRIPT'
#!/bin/bash
set -e
REPO="/mnt/backup/restic-repo"
# Pre-backup: dump databases
pg_dumpall > /tmp/db-backup.sql
mysqldump --all-databases > /tmp/mysql-backup.sql
# Run backup
restic -r "$REPO" backup \
/home/user \
/etc \
/var/www \
/tmp/db-backup.sql \
--exclude=".cache" \
--tag automated
# Cleanup temp files
rm -f /tmp/db-backup.sql /tmp/mysql-backup.sql
# Cleanup old snapshots
restic -r "$REPO" forget \
--keep-last 7 \
--keep-daily 30 \
--keep-weekly 12 \
--keep-monthly 6 \
--prune
# Check repository (weekly on Sundays)
if [ $(date +%u) -eq 7 ]; then
restic -r "$REPO" check
fi
SCRIPT
chmod +x /usr/local/bin/restic-backup.shBorg
Repository Init & Create
# Install borg
apt install -y borgbackup
# Initialize a repository
borg init --encryption=repokey-blake2 /mnt/backup/borg-repo
# Initialize remote repository via SSH
borg init --encryption=repokey-blake2 user@backup-server:/srv/borg-repo
# Encryption modes:
# repokey : Key stored in repo, password protects it (recommended)
# repokey-blake2 : Same but with BLAKE2b hash (faster)
# keyfile : Key stored in /etc/borg/keys
# keyfile-blake2 : Key file + BLAKE2b
# none : No encryption (not recommended)
# Create first archive
borg create /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/
# Create with compression
borg create --compression lz4 /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/
# Compression levels: lz4 (fast), zstd (balanced), zlib (compatible), lzma (max)
borg create --compression zstd,3 /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/
# Exclude patterns
borg create /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/ \
--exclude '*.tmp' \
--exclude '.cache' \
--exclude 'node_modules'
# Exclude from file
borg create /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/ \
--exclude-from /etc/borg-exclude.txt
# Verbose output
borg create -v --stats /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/
# Show files as they're added
borg create -v --list /mnt/backup/borg-repo::backup-$(date +%Y%m%d) /home/user/Prune Patterns
# List archives
borg list /mnt/backup/borg-repo
# Show archive details
borg info /mnt/backup/borg-repo::backup-20240101
# Prune old archives (GFS retention)
borg prune --list /mnt/backup/borg-repo \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6
borg prune --list /mnt/backup/borg-repo \
--keep-within 7d \ # Keep all archives from last 7 days
--keep-daily 30 \ # Keep 30 daily
--keep-weekly 12 \ # Keep 12 weekly
--keep-monthly 12 \ # Keep 12 monthly
--keep-yearly 5 # Keep 5 yearly
# Prune with dry-run (show what would be deleted)
borg prune --list --dry-run /mnt/backup/borg-repo --keep-daily 30
# Prune remote repository
borg prune --list user@backup-server:/srv/borg-repo --keep-daily 30 --keep-weekly 12Mount & Extract
# Mount an archive as FUSE filesystem
borg mount /mnt/backup/borg-repo::backup-20240101 /mnt/borg-mount
# Mount entire repository (all archives)
borg mount /mnt/backup/borg-repo /mnt/borg-mount
# Structure: /mnt/borg-mount/{archive-name}/path/to/file
# Unmount
borg umount /mnt/borg-mount
# or
fusermount -u /mnt/borg-mount
# Extract entire archive to current directory
cd /restore/location
borg extract /mnt/backup/borg-repo::backup-20240101
# Extract specific paths
borg extract /mnt/backup/borg-repo::backup-20240101 home/user/Documents/
borg extract /mnt/backup/borg-repo::backup-20240101 etc/nginx/
# Extract with dry-run (show what would be extracted)
borg extract --list /mnt/backup/borg-repo::backup-20240101
# Extract to different directory
cd /tmp/restore && borg extract /mnt/backup/borg-repo::backup-20240101Diff Between Archives
# Show differences between two archives
borg diff /mnt/backup/borg-repo::backup-20240101 backup-20240102
# Show only changed files
borg diff /mnt/backup/borg-repo::backup-20240101 backup-20240102 \
--changed-only
# Show with content change details
borg diff /mnt/backup/borg-repo::backup-20240101 backup-20240102
# Output format:
# U /path/to/file (unchanged)
# A /path/to/new (added)
# R /path/to/removed (removed)
# M /path/to/modified (modified with size/diff info)
# Compare remote archives
borg diff user@server:/repo::backup-0101 backup-0102
# Summary of changes between archives
borg diff --json /mnt/backup/borg-repo::backup-20240101 backup-20240102 \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(f'Changed: {len(d)} files')"borgmatic Configuration
# Install borgmatic
apt install -y borgmatic
# Initialize borgmatic config
borgmatic config generate > /etc/borgmatic/config.yaml
# Example configuration: /etc/borgmatic/config.yaml
cat > /etc/borgmatic/config.yaml << 'YAML'
location:
source_directories:
- /home/user
- /etc
- /var/www
repositories:
- /mnt/backup/borg-repo
- user@backup-server:/srv/borg-repo
exclude_patterns:
- '*.tmp'
- '.cache'
- 'node_modules'
- '*.log'
- '.git'
storage:
compression: zstd,3
encryption_passphrase: "my-secret-passphrase"
archive_name_format: '{hostname}-{now:%Y-%m-%d-%H%M%S}'
retention:
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
keep_yearly: 2
consistency:
checks:
- name: repository
frequency: 1 week
- name: archives
frequency: 1 month
hooks:
before_backup:
- pg_dumpall > /tmp/db-backup.sql
- echo "Starting backup..."
after_backup:
- rm /tmp/db-backup.sql
- echo "Backup complete"
on_error:
- sendmail [email protected] <<< "Backup failed!"
YAML
# Run borgmatic
borgmatic
# Run with verbosity
borgmatic --verbosity 1
# Validate configuration
borgmatic config validate
# List all archives via borgmatic
borgmatic list
# Prune via borgmatic
borgmatic prune -v
# Extract via borgmatic
borgmatic extract --archive latest --destination /restore/location
# Create systemd timer for borgmatic
borgmatic install-crontab
# or use systemd:
# systemctl enable --now borgmatic.timerGeneral Backup Strategies
3-2-1 Rule
# The 3-2-1 backup rule:
# 3 copies of your data
# 2 different media types
# 1 copy offsite
# Example implementation:
# Copy 1: Production data (live)
# Copy 2: Local backup (NAS) — borg/restic to local repo
# Copy 3: Offsite backup — rsync/restic to remote server
# 2 media types:
# - Local NAS (hard disk)
# - Cloud/remote (different infrastructure)
# 1 offsite:
# - Remote server at colo
# - Cloud storage (S3, B2)
# - Physical media in safe deposit box
# Verify all three copies periodicallyGFS (Grandfather-Father-Son) Rotation
# Retention policy naming convention:
# Son: Daily backups (retain 7-30 days)
# Father: Weekly backups (retain 4-12 weeks)
# Grandfather: Monthly backups (retain 6-12 months)
# borgmatic example:
retention:
keep_daily: 7 # Son — keep 7 daily backups
keep_weekly: 4 # Father — keep 4 weekly backups
keep_monthly: 6 # Grandfather — keep 6 monthly backups
keep_yearly: 2 # Optional — keep yearly for compliance
# restic example:
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 2 \
--prune
# rsync with hardlink rotation:
# See the --link-dest incremental script above
# Similar retention can be achieved via directory naming + cronEncryption
# Encryption at rest:
# borg: --encryption=repokey-blake2 (AES-256-CTR + BLAKE2b)
# restic: built-in AES-256-GCM, automatically encrypted
# rsync: use LUKS on destination volume or GPG
# Encryption in transit:
# rsync over SSH (all traffic encrypted via SSH)
# restic over SFTP (SSH tunnel)
# restic over REST server (HTTPS with TLS)
# borg over SSH
# Encrypt rsync backups manually with GPG
tar czf - /home/user/ | gpg -c > /backup/home.tar.gz.gpg
# Decrypt: gpg -d /backup/home.tar.gz.gpg | tar xzf -
# LUKS encrypted backup volume
cryptsetup luksFormat /dev/sdb
cryptsetup open /dev/sdb backup-vol
mkfs.ext4 /dev/mapper/backup-vol
mount /dev/mapper/backup-vol /mnt/backup
# Now run borg/restic/rsync to /mnt/backupVerification & Restore Testing
# borg check (repository integrity)
borg check /mnt/backup/borg-repo
# borg check with archive data verification
borg check --verify-data /mnt/backup/borg-repo
# restic check
restic -r /mnt/backup/restic-repo check
# restic check with full data read
restic -r /mnt/backup/restic-repo check --read-data
# Test restore to a temporary location
borg extract --dry-run /mnt/backup/borg-repo::latest
restic restore latest --dry-run --target /tmp/test-restore
# Full restore test script
cat > /usr/local/bin/test-restore.sh << 'SCRIPT'
#!/bin/bash
TEST_DIR=$(mktemp -d)
echo "Testing restore to $TEST_DIR"
# Restore a small set of files
borg extract /mnt/backup/borg-repo::latest \
home/user/Documents/important.docx \
--destination "$TEST_DIR"
# Verify the restored file
if [ -f "$TEST_DIR/home/user/Documents/important.docx" ]; then
echo "RESTORE TEST: PASSED"
else
echo "RESTORE TEST: FAILED"
fi
# Cleanup
rm -rf "$TEST_DIR"
SCRIPT
chmod +x /usr/local/bin/test-restore.sh
# Schedule weekly restore tests
# @weekly /usr/local/bin/test-restore.shSystem Image vs File-Level vs Database Dumps
# System Image Backup (bare-metal restore)
# Tools: dd, clonezilla, fox clone, Veeam
# Backs up entire disk including OS, bootloader, partitions
# Fast restore to identical hardware
# Large storage requirement, not granular
# Full disk image with dd
dd if=/dev/sda of=/backup/sda-disk.img bs=4M status=progress
# With compression and progress
dd if=/dev/sda bs=4M | gzip > /backup/sda-disk.img.gz
# Restore: gunzip -c /backup/sda-disk.img.gz | dd of=/dev/sda bs=4M
# File-Level Backup (daily operations)
# Tools: rsync, borg, restic, duplicity
# Granular restore of individual files
# Efficient incremental backups
# Cannot restore bare metal without reinstall
# Database Dumps (application-consistent)
# PostgreSQL
pg_dump dbname > db-$(date +%Y%m%d).sql
pg_dumpall > full-db-$(date +%Y%m%d).sql
pg_dump --format=custom dbname > db-$(date +%Y%m%d).dump
# MySQL/MariaDB
mysqldump dbname > db-$(date +%Y%m%d).sql
mysqldump --all-databases > full-$(date +%Y%m%d).sql
mysqldump --single-transaction dbname > db-$(date +%Y%m%d).sql
# Combined approach: files + databases
# 1. Dump databases to /tmp
# 2. Back up /home, /etc, /var/www + /tmp/dumps with borg/restic
# 3. Prune old backups
# 4. Optionally create periodic system image (e.g., monthly)
# Recommended strategy:
# Daily: File-level (borg/restic) + database dumps
# Weekly: File-level + backups to offsite
# Monthly: System image (clonezilla/dd) + file-level
# Quarterly: Full restore test to verify everything