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.
Count your copies, media types, and off-site location.
restic check
At least 3 copies, on 2 media, with 1 off-site.
If the app won't boot on restored data, the backup isn't done.
Layer path
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.
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.
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.
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.
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.
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.
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
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 table
| Symptom | Evidence to collect | Likely layer | Next action |
|---|---|---|---|
| Ransomware/delete wiped 'all' backups | Whether any copy was immutable/offline | No air-gapped/immutable copy | Add object-lock or an offline copy (the '1'). |
| Restored DB won't open / is corrupt | How the DB was captured (live copy vs dump) | Crash-inconsistent DB backup | Use logical dumps or quiesce the container. |
| Old snapshots fail to read | restic check / --read-data results | Repo bit rot | Schedule integrity checks; keep redundant copies. |
| Backups exist but nobody has restored one | Date of last restore drill | Unproven recoverability | Run a scratch restore + app boot test. |
| Can't decrypt the backup in an emergency | Where the key/passphrase lives | Key not escrowed | Store the key separately and test it. |
Commands and settings paths
Check repository structure
restic checkWhere: 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 everythingWhere: 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 .backupWhere: 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 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 builderRelated 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