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
| Copy | Tool | Location |
|---|---|---|
| 1 (primary) | — | Live data on primary disk |
| 2 (local) | restic | External USB drive or separate NAS |
| 3 (off-site) | restic | Remote 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.