Skip to content
HomeTechOps

Self-Hosting

A home-server backup you've actually restored

Prove your backup recovers, don't assume it — 3-2-1 extended to 3-2-1-1-0, application-consistent database dumps, restic check, and a real restore drill.

Start here: Reach 3-2-1

Ensure 3 copies, 2 media, 1 off-site.

Problem summary

Most home-server operators conflate 'the backup job ran' with 'I have a recoverable backup' — different claims. A job can succeed for months while producing a non-restorable archive: a torn database write, a forgotten volume, bit rot, an expired key. The operator-grade answer extends 3-2-1 to 3-2-1-1-0 — one copy that's immutable/offline/air-gapped, and zero errors after a verified restore test. The two disciplines that make it real: application-consistent database backups (stop the container or dump with `pg_dump`/`mysqldump`/SQLite `.backup` — never `cp` a hot DB file), and a restore drill that recovers to a scratch location and proves the app boots, not just that bytes copied.

Operator snapshotEvidence first
First proof

Count your copies, media types, and off-site location.

Screen to open

restic check

Expected signal

At least 3 copies, on 2 media, with 1 off-site.

Stop boundary

If the app won't boot on restored data, the backup isn't done.

Layer path

1'The backup job ran' and 'I have a recoverable backup' are different claims. A job can succeed for months while producing a non-restorable archive — a torn DB write, a forgotten volume, bit rot, an expired key.
23-2-1 (3 copies, 2 media, 1 off-site) answers how-many-and-where but nothing about ransomware or recoverability. 3-2-1-1-0 adds one immutable/offline/air-gapped copy and zero errors after a verified restore test.
3Databases need application-consistent backups: copying a live SQLite/Postgres/MySQL file mid-write captures torn pages that may not reopen. Stop the container or take a logical dump, then back up the dump.
4The discipline that makes 3-2-1-1-0 real is the restore drill: recover to a scratch location, verify checksums, and boot the app against the restored data — service-level proof, not a file copy.
Runbook

Step-by-step runbook

Start here. Do each check in order, compare it to the expected result, and stop when the evidence explains the failure or the safe stop point applies.

1

Reach 3-2-1

Check: Ensure 3 copies, 2 media, 1 off-site.

Expected result: A single event can't destroy everything.

If not: Add whatever copy/media/location is missing.

2

Add the immutable '1'

Check: Make one copy object-locked, append-only, or offline.

Expected result: Ransomware/delete can't reach every copy.

If not: Object-lock a bucket or keep an unplugged drive.

3

Make DB backups consistent

Check: Dump databases or back them up while stopped.

Expected result: DB backups reopen cleanly.

If not: Stop copying live DB files.

4

Schedule integrity checks

Check: Run the "Check repository structure" command below regularly and --read-data periodically.

Expected result: Corruption is caught early.

If not: Bound --read-data with a subset on large repos.

5

Run a restore drill

Check: Restore to scratch, verify checksums, boot the app.

Expected result: Recovery is proven end-to-end (the '0').

If not: Never restore over live data during a drill.

Safe stop: If the app won't boot on restored data, the backup isn't done.

6

Escrow the key

Check: Store the repo key/passphrase separately and test access.

Expected result: You can actually decrypt in an emergency.

If not: A lost key makes the backup worthless.

Did this solve your problem?

Your answer helps us improve this page.

No account or personal details. We store the page, answer, optional reason and day with a 90-day retention period. A random reference in this browser prevents repeat votes. Feedback privacy

Decision tree

Decision tree

If: All copies are online/mutable

Then: No ransomware/accidental-delete resistance.

Action: Add an immutable (object-lock/append-only) or offline copy.

Safe stop: Don't trust a backup set that one compromise can erase.

If: Databases are copied as live files

Then: Backups may be torn/inconsistent.

Action: Dump the DB or stop the container before backing up its files.

If: Repo never integrity-checked

Then: Silent corruption is possible.

Action: Run restic check regularly; --read-data periodically on a schedule you can afford.

If: Never restore-tested

Then: Recoverability is unproven.

Action: Restore to scratch, verify checksums, and boot the app.

Safe stop: Treat 'never restored' as 'no backup' for critical data.

If: Restore needs a key you don't have

Then: Encryption key not escrowed.

Action: Back up the key/passphrase separately and test access.

Evidence

Evidence table

SymptomEvidence to collectLikely layerNext action
Ransomware/delete wiped 'all' backupsWhether any copy was immutable/offlineNo air-gapped/immutable copyAdd object-lock or an offline copy (the '1').
Restored DB won't open / is corruptHow the DB was captured (live copy vs dump)Crash-inconsistent DB backupUse logical dumps or quiesce the container.
Old snapshots fail to readrestic check / --read-data resultsRepo bit rotSchedule integrity checks; keep redundant copies.
Backups exist but nobody has restored oneDate of last restore drillUnproven recoverabilityRun a scratch restore + app boot test.
Can't decrypt the backup in an emergencyWhere the key/passphrase livesKey not escrowedStore the key separately and test it.
Reference

Commands and settings paths

Check repository structure

restic check

Where: On the backup host (or any machine with repo access)

Expected: Reports no errors in the repository structure.

Failure means: Errors indicate metadata/index corruption.

Safe next step: Investigate before relying on the repo; keep a second copy.

Re-hash data to catch bit rot

restic check --read-data-subset=10% # or --read-data for everything

Where: On the backup host

Expected: The sampled (or full) pack data re-hashes cleanly.

Failure means: A hash mismatch means stored data is damaged.

Safe next step: Restore affected data from another copy; rotate media.

Take an application-consistent DB dump

pg_dump -Fc <db> > db.dump # or mysqldump / sqlite3 .backup

Where: On the DB host/container

Expected: A complete logical dump you can restore independently.

Failure means: A partial dump means the DB was unreachable or creds are wrong.

Safe next step: Back up the dump, not the live data file.

Hardware boundary

Hardware and platform boundary

Change only when

  • Add an immutable/offline copy as soon as the data matters (photos, documents, password vault).
  • Schedule periodic --read-data and a calendar'd restore drill once the stack is real.

Evidence that matters

  • 3-2-1-1-0: three copies, two media, one off-site, one immutable/offline, zero errors after a tested restore.
  • Application-consistent database dumps.
  • A restore drill that boots the app on restored data, plus an escrowed key.

Evidence that does not matter

  • Backing up disposable, regenerable artifacts (caches, thumbnails).
  • Raw RAID as a 'backup' — it's availability, not recovery.

Avoid

  • Trusting 'the job ran' as proof of recoverability.
  • Copying live database files.
  • Keeping every copy online and mutable.

Related tool

Use the linked tool to turn this runbook into a guided check for your exact setup.

Backup plan builder

Related problems

Last reviewed

2026-06-03 · Reviewed by HomeTechOps. Built from 2026-06 research verified against Veeam and Backblaze on 3-2-1-1-0, restic's integrity-check docs (check / --read-data / --read-data-subset), and NIST SP 800-34 on testing recovery rather than assuming it. The operator differentiator is the tested-restore '0' and application-consistent DB dumps, not just a green backup job.

Sources/assumptions

  • Assumes a self-hosted stack with one or more databases (Postgres/MySQL/SQLite) plus file data, backed up with a dedup tool (restic/Kopia/Borg are the common choice — stated only as supporting encrypted, deduplicated snapshots).
  • 3-2-1-1-0 framing is from vendor guidance (Veeam/Backblaze) — the extra '1' is the family immutable/offline/air-gapped, whose exact wording varies; the '0' is zero errors after a verified restore test. NIST SP 800-34 grounds the test-don't-assume principle.
  • restic check / --read-data / --read-data-subset are stated from restic's own docs as the integrity mechanism; equivalent verification exists in other tools.

Source-backed checks

HomeTechOps turns official docs and conservative safety rules into a shorter runbook. These links are the source trail for the page direction.

Planning a purchase?

We keep a source-backed, price-free comparison so you can buy once and right. No star ratings, every spec cited.

Mini PC for a Home Server: N150 vs MS-01 vs Tiny →

Get the deal & firmware alerts

Home Stack Field Notes: NAS deals, firmware changes worth acting on, restore-test reminders, and new decision guides — plus the capacity & backup sizing cheatsheets from our calculators. Unsubscribe anytime.

Subscribe on beehiiv