3-2-1 Backup Strategy for Your Homelab
A practical guide to the 3-2-1 backup rule — what it means, how to implement it, and how to actually recover when things go wrong.
I’ve watched data loss happen, at home and at work, and every time it was the same shape: the backup existed, and nobody had ever restored from it. 3-2-1 is the smallest rule that covers most of the ways this goes wrong.
3 copies of the data. 2 different media or systems. 1 of them somewhere else.
For a homelab that’s the live data, a local backup target, and one copy geographically separate. Three files on the same array is one copy.
Note
This is the general shape. What my own chain actually looks like today — which legs run when, and what deliberately isn’t backed up — is in the homelab tour.
The stack
| Layer | Example | What it’s for |
|---|---|---|
| Primary | Proxmox + Longhorn / local disks | Working data |
| Local backup | Synology NAS / Proxmox Backup Server | Fast restore, same building |
| Offsite | Cloud VPS, Hetzner StorageBox, S3-compatible storage | Survives the building |
The tools matter far less than people think. What matters is that the three copies exist, that the offsite one is encrypted, and that you have restored from at least one of them.
The ways it fails anyway
- Never testing restores. A backup you haven’t restored from is a hope.
- Backups on the same system as the data. That’s a second copy of a single point of failure.
- No encryption offsite. You’ve handed someone else your files.
- No versioning. A live mirror replicates the encryption as fast as ransomware writes it.
- No offsite copy. One fire, one flood, one theft.
Caution
Ransomware looks for backup files specifically, and a mounted backup share is the easiest target in the house. Without versioning or a copy that’s genuinely offline, there is nothing to recover from.
What actually helps: snapshots with a retention policy, at least one copy that’s offline or air-gapped, MFA on whatever manages the backups, and an alert when a job fails. That last one is cheap and catches the most common failure — backups that quietly stopped months ago.
Proxmox Backup Server, from any Linux host
The backup side. --keyfile is what makes the copy on the far end useless to anyone who takes the disk:
#!/bin/bash
export PBS_PASSWORD='token-secret-from-PBS'export PBS_USER_STRING='token-id-from-PBS'export PBS_SERVER='PBS-IP'export PBS_DATASTORE='DATASTORE_PBS'export PBS_REPOSITORY="${PBS_USER_STRING}@${PBS_SERVER}:${PBS_DATASTORE}"export PBS_HOSTNAME="$(hostname -s)"export PBS_KEYFILE='/root/pbscloud_key.json'
echo "Running PBS backup for $PBS_HOSTNAME ..."
proxmox-backup-client backup \ srv.pxar:/srv \ volumes.pxar:/var/lib/docker/volumes \ etc.pxar:/etc \ scripts.pxar:/usr/local/bin \ --keyfile "$PBS_KEYFILE" \ --skip-lost-and-found \ --repository "$PBS_REPOSITORY"
proxmox-backup-client list --repository "$PBS_REPOSITORY"echo "Done."The restore side
Written now, rather than during the outage:
#!/bin/bash
export PBS_PASSWORD='token-secret-from-PBS'export PBS_USER_STRING='token-id-from-PBS'export PBS_SERVER='PBS_IP'export PBS_DATASTORE='DATASTORE_FROM_PBS'export PBS_KEYFILE='/root/pbscloud_key.json'export PBS_REPOSITORY="${PBS_USER_STRING}@${PBS_SERVER}:${PBS_DATASTORE}"
SNAPSHOT_PATH="$1"ARCHIVE_NAME="$2"RESTORE_DEST="$3"
14 collapsed lines
if [[ -z "$SNAPSHOT_PATH" || -z "$ARCHIVE_NAME" || -z "$RESTORE_DEST" ]]; then echo "Usage: $0 <snapshot_path> <archive_name> <destination>" echo "Example: $0 \"host/cloud/2025-01-22T15:19:17Z\" srv.pxar /root/restore-srv" exit 1fi
mkdir -p "$RESTORE_DEST"
echo "=== PBS Restore ==="echo "Snapshot: $SNAPSHOT_PATH"echo "Archive: $ARCHIVE_NAME"echo "Destination: $RESTORE_DEST"echo "==================="
proxmox-backup-client restore \ "$SNAPSHOT_PATH" \ "$ARCHIVE_NAME" \ "$RESTORE_DEST" \ --repository "$PBS_REPOSITORY" \ --keyfile "$PBS_KEYFILE"
[[ $? -eq 0 ]] && echo "Restore completed." || echo "Restore failed (exit $?)."Note
Longhorn PVCs don’t go through PBS. Those are restored from the S3 backup target instead.
When one layer dies
| What failed | What you still have | What to do |
|---|---|---|
| Local NAS | Backup server + offsite | Replace hardware, rebuild the array, restore from whichever copy is closest, then confirm the next scheduled job succeeds |
| Offsite target | Everything local | New instance, redeploy the client, restore the encryption key from the password manager, run the backup once by hand, confirm the cron is back |
| Backup server | NAS + offsite | Reinstall, restore the datastore config and mount point, check the datastore structure is intact (.chunks, namespaces), then pull from a surviving copy |
Each row assumes the other two copies are healthy. The reason 3-2-1 works is that it never asks you to bet on one of them.
What lives in the password manager
Restoring is gated on things that are not themselves in the backup:
- The encryption keyfile, e.g.
pbscloud_key.json— without it the offsite copy is noise - Token credentials for the backup system
- The backup paths for each layer
- The cron definitions, so the automation can be rebuilt from nothing
Keeping it honest
Monthly, run an integrity check over a random subset and read the logs for jobs that failed quietly. Quarterly, do a real restore of at least one backup and write down how long it took — that number is your actual recovery time, and it’s usually longer than anyone’s estimate.
Alert on failed jobs and on capacity thresholds. Running out of space is the most common way a backup stops working without anyone noticing.