3-2-1 Backups with restic and borgbackup

Automated, encrypted, tested backups. Because backups you haven’t tested are not backups.

The 3-2-1 rule is a simple heuristic: keep 3 copies of your data, on 2 different media types, with 1 copy off-site. The “media types” part is less about the physical medium and more about failure independence: if your NAS dies, your local USB drive and your remote host are unaffected. This guide implements the rule using restic for local and remote backups and borgbackup as an alternative, with systemd timers for automation.

Backup targets

CopyToolLocation
1 (primary)Live data on primary disk
2 (local)resticExternal USB drive or separate NAS
3 (off-site)resticRemote server via SFTP or cloud

1. restic: setup

Install

apt install -y restic
restic version

Initialise repositories

# Local USB repository (mounted at /mnt/backup-usb)
restic -r /mnt/backup-usb/homelab init

# Remote repository via SFTP
restic -r sftp:user@backup-server.lan:/backups/homelab init

Use a strong, unique password for each repository and store it in a password manager. The repository encryption key is derived from this password; losing it means losing access to the backup permanently.

Store credentials for automation

mkdir -p /etc/restic
echo 'RESTIC_REPOSITORY=/mnt/backup-usb/homelab' > /etc/restic/local.env
echo 'RESTIC_PASSWORD_FILE=/etc/restic/local.passwd' >> /etc/restic/local.env
echo 'your-strong-password' > /etc/restic/local.passwd
chmod 600 /etc/restic/local.env /etc/restic/local.passwd

2. restic: first backup

source /etc/restic/local.env
restic backup   /home   /etc   /srv   --exclude /srv/data/jellyfin/cache   --tag homelab   --verbose

Check what was backed up:

restic snapshots
restic stats latest

3. restic: incremental backups and retention

restic stores content-addressed chunks, so identical data across snapshots is deduplicated. After the first backup, incrementals are fast:

restic backup /home /etc /srv --tag homelab

Apply a retention policy to avoid accumulating snapshots indefinitely:

restic forget   --keep-daily 7   --keep-weekly 4   --keep-monthly 6   --prune

This keeps daily snapshots for the past week, weekly for a month, and monthly for six months, then removes unreferenced data chunks.

4. restic: Docker volume backups

For Docker volumes, stop or pause the container if the data must be consistent, then back up the volume’s data directory:

docker compose -f /srv/compose/main/compose.yml stop nextcloud
restic backup /srv/data/nextcloud --tag nextcloud
restic backup /var/lib/docker/volumes/main_nc-db --tag nextcloud-db
docker compose -f /srv/compose/main/compose.yml start nextcloud

For databases, take a logical dump rather than a filesystem snapshot where possible:

docker compose exec nextcloud-db   mysqldump -u ncuser -p"$(cat /srv/compose/main/secrets/nextcloud_db_pw.txt)"   nextcloud > /tmp/nextcloud-$(date +%Y%m%d).sql
restic backup /tmp/nextcloud-$(date +%Y%m%d).sql --tag db-dump

5. Automating with systemd timers

Create /etc/systemd/system/restic-local.service:

[Unit]
Description=restic local backup
After=network.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/local.env
ExecStart=/usr/bin/restic backup     /home /etc /srv     --exclude /srv/data/jellyfin/cache     --tag homelab
ExecStartPost=/usr/bin/restic forget     --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
StandardOutput=journal
StandardError=journal

Create the timer at /etc/systemd/system/restic-local.timer:

[Unit]
Description=Run restic local backup daily

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=30min
Persistent=true

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now restic-local.timer
systemctl list-timers restic-local.timer

6. borgbackup: an alternative

borgbackup is an excellent alternative with built-in compression, a mature ecosystem, and strong performance on spinning rust. Setup:

apt install -y borgbackup
borg init --encryption=repokey /mnt/backup-usb/borg-homelab
# Export the key: store it separately from the backup medium
borg key export /mnt/backup-usb/borg-homelab /etc/borg/homelab.key

Backup command:

borg create   --verbose   --filter AME   --list   --stats   --show-rc   --compression lz4   --exclude /proc   --exclude /sys   --exclude /dev   --exclude /run   /mnt/backup-usb/borg-homelab::homelab-{now:%Y-%m-%dT%H:%M:%S}   /home /etc /srv

Prune old archives:

borg prune   --list   --keep-daily 7   --keep-weekly 4   --keep-monthly 6   /mnt/backup-usb/borg-homelab

7. Testing restores

A backup strategy that has never produced a successful restore is not a backup strategy. Test monthly:

# restic: list and restore a specific snapshot
restic snapshots
restic restore <snapshot-id> --target /tmp/restore-test/
diff -r /home/myuser /tmp/restore-test/home/myuser

# borgbackup: mount the archive and compare
borg mount /mnt/backup-usb/borg-homelab::homelab-2024-01-15T02:00:00 /mnt/borg-test
diff -r /srv/data /mnt/borg-test/srv/data
borg umount /mnt/borg-test

If you cannot restore from your backup today, you do not have a backup. Schedule a restore test as a recurring calendar event and treat a failed test as an incident.