Homelab Backup Strategy: The 3-2-1 Rule Done Right

A solid homelab backup strategy follows the 3-2-1 rule: keep 3 copies of your data, on 2 different types of media, with 1 copy offsite. In practice that means restic or borgbackup writing encrypted, deduplicated snapshots to local disk and a remote target, plus a real plan for your Docker volumes and databases.

Most homelab "backups" I see aren't backups. They're a single external drive that gets plugged in once a month, or a Proxmox node with RAID that the owner thinks is protecting them. Neither survives the failure modes that actually happen: a bad rm -rf, a ransomware hit, a power surge that takes out the whole rack, or a house fire. The 3-2-1 rule exists to cover those. Let's build a homelab backup strategy that actually holds up when something goes wrong.

What is the 3-2-1 backup rule?

The 3-2-1 backup rule was coined by photographer Peter Krogh in the mid-2000s in The DAM Book: Digital Asset Management for Photographers. It's since become the baseline everyone references, including the US government's CISA. The rule is three numbers:

  • 3 copies — your live data plus two backups. One copy is one failure away from zero.
  • 2 media types — don't keep both backups on the same kind of storage in the same box. Two drives on the same NAS share a power supply, a controller, and a ransomware blast radius.
  • 1 offsite — a copy that a fire, flood, theft, or lightning strike at your house can't reach.

You'll also see the extended 3-2-1-1-0 variant: add 1 offline or immutable copy (so ransomware can't encrypt your backups too) and 0 restore errors (you've actually tested a restore). For a homelab, 3-2-1 is the floor and the immutability idea is worth stealing.

Why RAID and cloud sync aren't backups

Two things I have to say every time this comes up.

RAID is not a backup. RAID protects against a drive dying. It does nothing about deleted files, corrupted files, a failed controller writing garbage to every disk, or you fat-fingering a command. A mirror faithfully mirrors your mistake to both drives instantly.

Google Drive / Nextcloud / Syncthing sync is not a backup either. Sync propagates changes — including deletions and ransomware encryption — to every synced copy. I've watched someone's cloud "backup" get overwritten with encrypted files because the sync client did exactly what it was designed to do. Versioning helps a little, until the version history ages out. A backup is a separate, point-in-time copy you can roll back to. Sync is not that.

Building a homelab backup strategy around 3-2-1

Here's a concrete mapping that works for most homelabs:

  • Copy 1 (live): your VMs, containers, and config on the host.
  • Copy 2 (local, different media): encrypted snapshots pushed to a NAS or a dedicated backup drive — ideally a different machine than the one running your workloads.
  • Copy 3 (offsite): the same snapshots replicated to cloud object storage (Backblaze B2, Wasabi, an S3 bucket) or to a box at a friend's house over SFTP.

The tool doing the heavy lifting should give you encryption, deduplication, and cheap incrementals so the offsite copy stays affordable. That's restic or borgbackup. Both are excellent; I'll show you both and tell you which I reach for.

restic tutorial: encrypted, deduplicated snapshots

restic is a single static Go binary. No server component, no daemon, encryption on by default, and it talks to local disk, SFTP, and S3-compatible object storage natively. The current release is 0.19.0 (June 2026). This restic tutorial covers the whole loop: init, back up, verify, prune, restore.

Install it and initialize a repository. A repository is just the encrypted store — it can be a local path, an SFTP target, or a cloud bucket.

# Debian/Ubuntu
sudo apt install restic

# Point restic at a repo and a password file
export RESTIC_REPOSITORY=/mnt/backups/restic-repo
export RESTIC_PASSWORD_FILE=/root/.restic-pass

restic init

Now take a snapshot. Tag it so you can tell daily runs from manual ones later.

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

Realistic output looks like this — note the "stored" figure is smaller than the processed size because of deduplication and compression (repository format version 2):

repository a14e5863 opened (version 2, compression level auto)
start backup on [/srv /home /etc]
Files:        5307 new,     0 changed,     0 unmodified
Added to the repository: 1.200 GiB (1.103 GiB stored)
processed 5307 files, 1.720 GiB in 0:12
snapshot 40dc1520 saved

Run it again tomorrow and it's fast and tiny — only changed data gets written. List your snapshots and verify repository integrity:

restic snapshots
restic check

Run restic check regularly. It verifies the repo's internal structure so you find corruption before you need to restore, not during.

Retention: keep a sensible window and prune the rest. --prune actually reclaims the space.

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

And the part everyone forgets to practice — restoring:

# Restore the latest snapshot to a scratch directory
restic restore latest --target /tmp/restore

# Or mount the whole repo read-only and grab individual files
restic mount /mnt/restic

restic has no scheduler of its own — wire the backup command into a systemd timer or cron. Official docs: restic.readthedocs.io.

borgbackup: the main alternative

borgbackup gives you the same core wins — deduplication, compression, authenticated encryption — and is especially efficient over SSH when Borg is installed on both ends, because dedup happens remotely instead of shipping everything over the wire.

Which Borg version should you use?

Use the 1.4.x stable series. As of writing, 1.4.4 is current (released 2026-03-19), with 1.2.9 as oldstable. Borg 2.0 is still in beta (2.0.0b21) and the project explicitly says not to use it on production repositories. Borg 2.0 is worth watching — it changes the CLI (repositories move to a -r flag and archive names no longer need to be unique), adds rclone and S3 backends, and needs less RAM — but for backups you actually depend on, stay on 1.4.x for now.

The 1.x workflow, using the classic repo::archive syntax:

sudo apt install borgbackup

# repokey stores the key inside the repo — export a copy or you can be locked out
borg init --encryption=repokey /mnt/backups/borg-repo
borg key export /mnt/backups/borg-repo /root/borg-key-backup.txt

# Create an archive named by host and timestamp
borg create /mnt/backups/borg-repo::'{hostname}-{now}' /srv /home /etc

borg list /mnt/backups/borg-repo

# Prune old archives, then compact to actually free disk (1.2+)
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 /mnt/backups/borg-repo
borg compact /mnt/backups/borg-repo

The borg key export step matters: with repokey, losing the repo means losing the key. Keep that export somewhere separate. Restore with borg extract or browse with borg mount. Release info: borgbackup.org/releases.

restic vs borgbackup: which should you pick?

Factor restic borgbackup
Install Single static binary Python package; binary must be on both ends for efficient SSH
Cloud / object storage Native S3, B2, Azure, plus SFTP SFTP in 1.x; rclone/S3 arriving in 2.0 (beta)
Encryption Always on Optional, recommended (repokey/keyfile)
Current version 0.19.0 (stable) 1.4.4 (stable); 2.0 in beta
Best fit Offsite to cloud object storage Offsite to a Linux box you control over SSH

My take: for most homelabs, use restic. The single binary and first-class object-storage support make the offsite "1" trivial and cheap. Reach for borgbackup when your offsite target is a Linux machine you own over SSH, where its remote-side dedup shines. Pick one and commit — running both just doubles the things that can silently break.

How do I back up Docker volumes?

Container data lives in named volumes, and there's no single built-in "back up this volume" command. The reliable pattern is a throwaway container that mounts the volume and tars it. But how you back up Docker volumes depends on whether the data is a database.

Config and file volumes: tar them

For static config, media, or app files, mount the volume read-only into an alpine container and archive it. The -C /data plus trailing . keeps the archive paths clean:

docker run --rm \
  -v myapp_config:/data:ro \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/myapp_config.tar.gz -C /data .

To restore into a fresh (or existing) volume, wipe the target first so you don't end up with a mix of old and new files:

docker run --rm \
  -v myapp_config:/data \
  -v "$(pwd)":/backup \
  alpine sh -c "rm -rf /data/* /data/..?* /data/.[!.]* 2>/dev/null; tar xzf /backup/myapp_config.tar.gz -C /data"

Database volumes: dump them, don't tar a live one

Do not tar the volume of a running database. You'll capture a half-written, inconsistent on-disk state and the restore may not import. Use the database's own dump tool for a consistent snapshot:

# PostgreSQL
docker exec -t my-postgres pg_dumpall -U postgres > db-$(date +%F).sql

# MySQL / MariaDB (MariaDB images: use mariadb-dump)
docker exec my-mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' > db-$(date +%F).sql

Then let restic pull the dump straight from stdin — no intermediate file to clean up:

docker exec -t my-postgres pg_dumpall -U postgres | restic backup --stdin --stdin-filename postgres.sql

Those dumps and tarballs are just files, so they roll into your restic or borg repo like everything else — and inherit the same 3-2-1 offsite copy. Docker's own reference on volumes: docs.docker.com.

Getting the offsite copy (the "1")

The offsite copy is the part people skip, and it's the one that saves you from fire and theft. Cheapest reliable option: point a second restic run at Backblaze B2 or Wasabi. Because restic encrypts before upload, the provider never sees your data.

export RESTIC_REPOSITORY=s3:s3.us-west-004.backblazeb2.com/your-bucket
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
restic init
restic backup /srv /home /etc --tag offsite

Whatever you choose, use an app key scoped to that one bucket, and if the provider supports object lock / immutability, turn it on so a compromised host can't delete its own offsite backups.

Test your restores, or you don't have backups

This is the whole game. A backup you've never restored is a hope, not a backup. Once a month, restore a snapshot to a scratch directory and actually open a few files. Restore a database dump into a throwaway container and confirm it imports. I've had a backup job report success for weeks while silently excluding the one directory that mattered — I only caught it because a test restore came up empty. Put a reminder on the calendar and do it.

FAQ

Is RAID a backup?

No. RAID protects against a disk failing, but it happily replicates deletions, corruption, and ransomware to every disk in the array. It's uptime insurance, not a backup. You still need 3-2-1 on top of it.

How often should I run homelab backups?

Daily for anything you'd hate to lose, driven by a cron job or systemd timer. Match the frequency to how much work you're willing to redo — hourly for a busy database, daily for config and media is plenty for most homelabs.

Do I need to encrypt my backups?

For the offsite/cloud copy, yes — you're handing data to someone else's disks. restic encrypts by default; with borgbackup use repokey or keyfile. Store the passphrase (and Borg key export) somewhere separate from the repo, like a password manager.

restic or borgbackup for a homelab?

restic if your offsite target is cloud object storage — the single binary and native S3 support make it simple. borgbackup if you're backing up to a Linux box you control over SSH, where its remote-side deduplication is more efficient. Both are solid; don't run both.

Can I back up a running database container by copying its volume?

No. A live database volume is mid-write and the copy will likely be inconsistent. Use pg_dumpall, mysqldump, or mariadb-dump via docker exec for a clean, restorable dump, then back up that dump.