Self-Hosted Backups with BorgBackup and Restic: A Practical Comparison
You're running a dozen self-hosted services. Immich has 50,000 irreplaceable family photos. Paperless-ngx holds years of scanned documents. Your Vaultwarden instance has every password you own. And it's all sitting on one machine.
If that disk dies tomorrow, what happens?
Self-hosting without backups is just renting time before a disaster. BorgBackup and Restic are the two most popular open-source backup tools in the homelab community, and they both solve this problem well — but differently.
Why Not Just rsync?
rsync copies files. That's it. It doesn't deduplicate, doesn't compress efficiently, doesn't encrypt, doesn't do versioning, and doesn't help you recover from ransomware or accidental deletion. You end up with a full copy of everything, every time, with no way to roll back.
BorgBackup and Restic are purpose-built backup tools that give you:
- Deduplication — Only store the data that actually changed
- Encryption — Your backups are unreadable without your key
- Versioning — Browse and restore any point-in-time snapshot
- Compression — Significantly reduce storage requirements
- Integrity verification — Detect silent data corruption
BorgBackup vs Restic
| Feature | BorgBackup | Restic |
|---|---|---|
| Language | Python/C | Go |
| Deduplication | Content-defined chunking | Content-defined chunking |
| Encryption | AES-256-CTR + HMAC-SHA256 | AES-256-CTR + Poly1305 |
| Compression | lz4, zstd, zlib, lzma | zstd (since 0.16) |
| Remote backends | SSH only (native) | S3, B2, SFTP, rclone, REST |
| Speed | Faster for local/SSH | Faster for cloud backends |
| Repo format | Append-only capable | Append-only capable |
| Mount backups | FUSE mount | FUSE mount |
| Windows support | No | Yes |
| Maturity | 2010 (as attic) | 2015 |
| Parallel processing | Single-threaded | Multi-threaded |
Choose BorgBackup if: You're backing up to a local NAS or another machine over SSH, want maximum compression options, and are Linux-only.
Choose Restic if: You want to back up to cloud storage (Backblaze B2, S3, etc.), need Windows support, or want simpler multi-backend configuration.
Setting Up BorgBackup
Install
# Debian/Ubuntu
sudo apt install borgbackup
# Fedora
sudo dnf install borgbackup
# Arch
sudo pacman -S borg
Create a repository
# Local repository
borg init --encryption=repokey /mnt/backup/borg-repo
# Remote repository (over SSH)
borg init --encryption=repokey user@backup-server:/path/to/repo
Save the encryption key somewhere safe:
borg key export /mnt/backup/borg-repo /safe/location/borg-key.txt
If you lose this key and the passphrase, your backups are gone forever.
Create a backup
borg create \
/mnt/backup/borg-repo::'{hostname}-{now:%Y-%m-%d_%H:%M}' \
/home \
/etc \
/var/lib/docker/volumes \
--exclude '*.tmp' \
--exclude '/home/*/.cache' \
--compression zstd,3
Prune old backups
borg prune \
/mnt/backup/borg-repo \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6
Automate with a script
#!/bin/bash
set -euo pipefail
export BORG_REPO="/mnt/backup/borg-repo"
export BORG_PASSPHRASE="your-passphrase" # or use BORG_PASSCOMMAND
BACKUP_NAME="{hostname}-{now:%Y-%m-%d_%H:%M}"
echo "Starting backup..."
borg create \
"::$BACKUP_NAME" \
/home /etc /var/lib/docker/volumes \
--exclude '*.tmp' \
--exclude '/home/*/.cache' \
--compression zstd,3 \
--stats
echo "Pruning old backups..."
borg prune \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--stats
echo "Verifying repository..."
borg check
echo "Backup complete."
Run this daily via cron or a systemd timer.
Setting Up Restic
Install
# Debian/Ubuntu
sudo apt install restic
# Fedora
sudo dnf install restic
# Or download the binary
wget https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2
bunzip2 restic_linux_amd64.bz2
chmod +x restic_linux_amd64
sudo mv restic_linux_amd64 /usr/local/bin/restic
Create a repository
# Local
restic init --repo /mnt/backup/restic-repo
# Backblaze B2
export B2_ACCOUNT_ID="your-account-id"
export B2_ACCOUNT_KEY="your-account-key"
restic init --repo b2:bucket-name:restic
# S3-compatible
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
restic init --repo s3:s3.amazonaws.com/bucket-name/restic
Create a backup
restic backup \
/home \
/etc \
/var/lib/docker/volumes \
--exclude '*.tmp' \
--exclude '/home/*/.cache' \
--repo /mnt/backup/restic-repo
Browse and restore
# List snapshots
restic snapshots --repo /mnt/backup/restic-repo
# Restore a specific snapshot
restic restore latest --target /tmp/restore --repo /mnt/backup/restic-repo
# Mount and browse (FUSE)
restic mount /mnt/restic-browse --repo /mnt/backup/restic-repo
Forget old snapshots
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune \
--repo /mnt/backup/restic-repo
Building a 3-2-1 Backup Strategy
The 3-2-1 rule: 3 copies of your data, on 2 different storage types, with 1 copy off-site.
Here's how to implement it for a homelab:
Copy 1: Primary data
Your running services. This is where data lives day-to-day.
Copy 2: Local backup
BorgBackup or Restic to a separate drive or NAS on your local network. Fast to backup, fast to restore. Protects against single-disk failure.
Copy 3: Off-site backup
Restic to Backblaze B2, or BorgBackup to a remote server via SSH. Protects against fire, theft, or catastrophic hardware failure.
Cost for off-site: Backblaze B2 costs $6/TB/month for storage. A typical homelab with 500 GB of critical data costs about $3/month for off-site backup — cheap insurance.
Backing Up Docker Volumes
Most self-hosted services run in Docker. Their data lives in volumes:
# Find where volumes are stored
docker volume inspect my_volume | jq '.[0].Mountpoint'
# Usually: /var/lib/docker/volumes/my_volume/_data
# Backup all volumes
restic backup /var/lib/docker/volumes/ --repo /mnt/backup/restic-repo
Important: Some services (databases, especially) shouldn't be backed up by copying files while they're running. You'll get corrupted data. Instead:
# PostgreSQL (used by Immich, Gitea, etc.)
docker exec my-postgres pg_dumpall -U postgres > /tmp/pg-backup.sql
# MariaDB/MySQL
docker exec my-mariadb mysqldump -u root -p --all-databases > /tmp/mysql-backup.sql
# Then include the dump files in your backup
Monitoring Backups
A backup that silently fails is worse than no backup — it gives you false confidence. Set up monitoring:
- borgmatic (for Borg) or runrestic (for Restic) — wrappers that add logging and error reporting
- Healthchecks.io — Free monitoring service; ping a URL after each successful backup; get alerted if the ping stops
- Uptime Kuma — Self-hosted alternative; monitor your backup cron jobs
- ntfy — Push notifications on backup success/failure
Common Mistakes
Never testing restores — A backup you haven't tested is a hope, not a plan. Regularly restore a file or directory to verify everything works.
Backing up to the same disk — If the drive dies, you lose both the data and the backup.
Not backing up encryption keys — Store your Borg/Restic repository key and passphrase somewhere safe (printed on paper, in a password manager, or on a separate USB drive).
Skipping database dumps — Copying database files while the database is running often produces corrupt backups.
No off-site copy — Local backups protect against hardware failure but not against fire, theft, or ransomware that encrypts everything on your network.
The Bottom Line
BorgBackup and Restic are both excellent, production-grade backup tools. BorgBackup has stronger compression and a longer track record; Restic has better cloud backend support and multi-threaded performance. Many people use both — Borg for local backups and Restic for cloud.
The best backup system is the one you actually set up and automate. Pick one, configure it, schedule it, and verify it regularly. Your future self will thank you the first time a drive dies or you accidentally delete something important.