I am convinced that almost every self-hosting guide on the internet, including a couple I have written, is quietly setting you up to lose everything. We get you to the good part. The app boots, the dashboard loads, your photos sync, and you feel like a genius. Then the guide ends. Nobody tells you that you are now exactly one dead disk away from a very bad weekend, and that a real self-hosted backup strategy is the chapter every tutorial skips.
This post is that chapter. Not another recitation of the 3-2-1 rule with a stock photo of a hard drive. This is a per-service, database-consistent, restore-tested plan for the actual stack you are running: Immich, Vaultwarden, Home Assistant, n8n, raw Docker volumes, and a DigitalOcean droplet. I run that same kind of setup, a droplet plus a homelab hosting these exact services, so this is the guide I wish someone had handed me.
Two things bite people, and almost no guide warns you about either. First, you cannot just tar a running database and call it a backup, because you will happily archive a corrupt file and not find out until restore day. Second, a backup you have never restored is not a backup. It is a folder you feel good about. Those two ideas are the whole reason this post exists, and a working self-hosted backup strategy lives or dies on getting them right.
Whether your stack runs on a Linux home server you built for under $200 or a cloud droplet like the one I landed on after years of shared hosting, the rules below are the same. Grab a coffee. This is the unglamorous chore that turns “I host my own stuff” into “I host my own stuff and I’m not terrified.”
The 3-2-1 rule, and the part everyone fakes
Quick version, because you have heard it. 3-2-1 means three copies of your data, on two different kinds of media, with one copy offsite. The original on your server counts as one. A second copy on a NAS or a different drive counts as two. A copy somewhere that is not in your house counts as three.
Most people nail the first two and fake the third.
Here is the lie we tell ourselves. We buy a second drive, shove it in the same machine or the same closet, and call that “offsite.” It is not. A surge, a flood, a theft, or a single dumb rm -rf in the wrong directory takes out both copies at once. Same building means same fate.
What actually counts as offsite?
Offsite means a different physical location. Backblaze B2, an S3 bucket, a buddy’s NAS across town over Tailscale, a drive you keep at the office. Those count.
A DigitalOcean snapshot of your droplet, sitting in the same DigitalOcean datacenter as the droplet? That does not count, and we will get to exactly why later. A second SSD in the same tower does not count. Your phone’s photo roll is not a backup of your Immich server, it is the thing Immich was supposed to free you from.
So a second drive in the same box doesn’t count? Correct. Two copies, one room, one accident from zero. The whole point of the offsite leg is that it survives the disaster that eats your house. If your “self-hosted backup strategy” has all three copies under one roof, you have one copy wearing a disguise.
The mistake that turns a backup into a corrupt file: copying a running database
This is the part I want tattooed on every homelabber’s forearm. If you take nothing else from this post, take this.
Immich, Vaultwarden, and n8n all store their real data in a database. Immich uses Postgres. Vaultwarden and n8n lean on SQLite or Postgres depending on how you set them up. A database on disk is not a sleeping file. It is a living thing, holding writes in memory, flushing them in its own time, juggling a write-ahead log it has not committed yet.
Now picture tar walking that directory while the database is mid-flush. Tar reads the first file at 2:00:01, the fifth file at 2:00:04, and in those three seconds the database moved on. You get an archive of a database that never existed in that exact state. It can restore. It can also restore into a corrupt mess that crashes the second the app touches it, and you will not know which until you try.
I learned to respect this the boring way, by reading the Postgres docs instead of by losing data, and the docs are blunt about it: a filesystem-level copy of a running server is not guaranteed to be consistent. That is not me being paranoid. That is the people who wrote the database telling you not to do it.
Dump it or stop it: the only two safe options
There are exactly two safe ways to back up a database. Dump it, or stop it.
Dump it: use the database’s own export tool while it runs. pg_dump for Postgres asks the database nicely for a consistent snapshot and hands you a clean file. The database stays up. Your users never notice.
Stop it: shut the container down so nothing is writing, copy the volume cold, start it back up. Simple, bulletproof, and it costs you a minute of downtime.
Pick based on the service. Got a giant Immich Postgres you do not want to interrupt? Dump it live with pg_dump. Got a tiny config volume nobody is touching at 3 a.m.? Stop, copy, start. Both are correct. Tarring a hot database volume and hoping is the only wrong answer.
The stop-tar-restart pattern for a plain Docker volume
For a normal Docker volume that does NOT hold a database (a config folder, a media cache, app settings), the safe move is stop, tar, restart. Three steps.
# 1. stop the container so nothing writes mid-copy
docker compose stop myapp
# 2. tar the named volume from a throwaway helper container
docker run --rm \
-v myapp_data:/data:ro \
-v $(pwd):/backup \
alpine tar czf /backup/myapp_data-$(date +%F).tar.gz -C /data .
# 3. start it back up
docker compose start myapp
That helper container mounts the volume read-only, tars it into your current directory, and removes itself. A few seconds of downtime buys you a copy you can actually trust. If a few seconds of downtime is unacceptable for the service, that is your sign to use a live dump instead.
The per-service playbook: Immich, Vaultwarden, Home Assistant, n8n
Here is the meat. One service at a time, what to grab, and the gotcha that quietly ruins the backup if you miss it. The pattern repeats: back up the data, AND back up the key that decrypts it. Miss the key and you have archived gibberish.
Immich (don’t tar the Postgres volume)
Immich has two halves and you need both, separately. The Postgres database holds the metadata, albums, faces, and the map of what is what. The library folders hold the actual photo and video files. Backing up one without the other gets you a beautiful index pointing at nothing, or a pile of files with no idea what they are.
Dump the database with pg_dump reaching into the running Postgres container.
# dump Immich's Postgres from the running container
docker exec -t immich_postgres \
pg_dumpall --clean --if-exists --username=postgres \
| gzip > immich-db-$(date +%F).sql.gz
Then back up the library and upload folders as plain files (they are just files, no consistency trap there). On most setups that is the UPLOAD_LOCATION directory from your Immich .env, which holds the library, upload, profile, and thumbs folders. Grab the whole thing.
# back up the Immich library/upload tree (the actual photos)
tar czf immich-library-$(date +%F).tar.gz -C /path/to/UPLOAD_LOCATION .
Immich changes fast, and the exact dump command shifts between versions, so check the current official Immich backup docs against whatever version you are running before you trust this in production. The principle holds even when the syntax drifts: dump the DB, copy the files, keep them together. If you have not stood Immich up yet, our walkthrough for replacing Google Photos with a self-hosted setup is the place to start.
Vaultwarden (back up the keys or it’s all noise)
Vaultwarden is your passwords. Get this one wrong and you are not losing photos, you are losing the keys to your entire digital life. So pay attention here.
The trap is that the data directory holds way more than the database. Your vault contents are encrypted client-side with a key derived from your master password, so losing the rest of the directory does not lock you out of the vault forever. But that directory holds the pieces that make a restore actually work: the rsa_key files (historically rsa_key.pem and friends, though the filenames have shifted across versions) that sign your login tokens, plus your attachments, your sends, and your config. Back up the SQLite database alone and forget the rest, and your restore comes up logged out and broken, with active sessions dead and pending org invites invalidated. You can log back in with your master password, but you cherry-picked the backup and left half the service on the floor.
So back up the entire data directory, the whole thing, keys included.
# stop, copy the WHOLE data dir (db + rsa keys + attachments), restart
docker compose stop vaultwarden
tar czf vaultwarden-$(date +%F).tar.gz -C /path/to/vaultwarden/data .
docker compose start vaultwarden
Do not cherry-pick files out of that directory to “save space.” Save the directory. The RSA keys are a few kilobytes and they are the most important kilobytes you own. For what it is worth, this is the one service where I would also keep a copy on a drive you can physically touch, not only in the cloud, because a vault you cannot reach during an internet outage is its own special nightmare.
Home Assistant (find your emergency-kit key NOW)
Home Assistant actually did the hard part for you. It has a built-in backup system that produces an encrypted backup archive, and you can schedule it from inside the UI under Settings, then System, then Backups. Turn it on. Set a schedule. Send the resulting file offsite with the same restic job we cover below.
But here is the gotcha that catches people at the worst possible moment. When you enable encrypted backups, Home Assistant generates an encryption key, the “emergency kit,” and tucks it into the backup settings expecting you to save it immediately. It is easy to breeze right past. Lose that key and your beautifully scheduled, encrypted, offsite backups are a pile of noise you cannot decrypt.
So go find it right now. Seriously, open a new tab.
Settings, System, Backups, and locate your emergency kit / encryption key. Write it down on paper. Put it in your password manager. Put it somewhere that is NOT on the Home Assistant box itself, because if the box dies you need the key to restore and the key lived on the box that died. The menu wording moves between releases, so if those exact words are not there, hunt for “encryption key” or “emergency kit” in the backup settings. If you have not built a dashboard yet, our Home Assistant beginner guide covers the setup before you get here.
n8n (the encryption key is the backup)
n8n is the same story with a different name on the key. Your workflows export as JSON, which feels safe, but the credentials those workflows use (API tokens, database passwords, the keys to your other services) are stored encrypted. They are encrypted with N8N_ENCRYPTION_KEY, an environment variable n8n generates on first run if you do not set one yourself.
Lose that key and every stored credential is unrecoverable. Not “annoying to reset.” Gone. You will be re-authenticating every single integration by hand.
So back up the n8n data volume AND record the encryption key somewhere safe and separate.
# print the key n8n is using, then store it OFF the server
# (comes back empty if you never set it: the auto-generated key is then hiding
# in ~/.n8n/config inside the volume, and an empty result is your cue to set it)
docker exec n8n printenv N8N_ENCRYPTION_KEY
# back up the n8n data volume (workflows + encrypted credentials)
docker compose stop n8n
tar czf n8n-$(date +%F).tar.gz -C /path/to/n8n/data .
docker compose start n8n
The honest move is to SET N8N_ENCRYPTION_KEY yourself in your compose file from day one, with a value you generated and saved, so it can never surprise you. If you are setting n8n up fresh, our n8n beginner guide walks through that compose file. Notice the pattern by now? Every service in this stack has a key that is part of the backup. Immich’s database, Vaultwarden’s RSA keys, Home Assistant’s emergency kit, n8n’s encryption key. A self-hosted backup strategy that copies the data and forgets the key is a self-hosted backup strategy that fails on restore day.
Backing up a DigitalOcean droplet without fooling yourself
A lot of my own stack lives on a DigitalOcean droplet, so I have a soft spot for this one, and also a bone to pick with how people think about it. The confusion here is expensive, and DO does not exactly rush to clear it up.
Snapshot vs backup add-on vs a real offsite copy
Three different things wear similar names. A snapshot is a manual, point-in-time image of your droplet that you trigger yourself. Handy before a risky upgrade. Not a backup strategy, because you have to remember to take it, and it sits in the same datacenter as the droplet.
The DigitalOcean backup add-on is the paid option (roughly 20% on top of your droplet cost) that takes images automatically on DO’s schedule. Closer to a backup. Still living in the same DO datacenter as the thing it is backing up.
A real offsite copy is you pulling the actual application data, the database dumps and the file trees from the sections above, off the droplet entirely and into Backblaze B2 or somewhere DigitalOcean does not control. That is the only leg that survives your DO account getting locked, your region having a bad day, or a billing mishap nuking the droplet.
What the add-on quietly leaves out
Here is the part that bites people. The DigitalOcean backup add-on images the droplet’s main disk, but it does not back up attached block-storage volumes. If you did the sensible thing and put your big Immich library on a separate block-storage volume to save money, that data is sitting outside the backup you are paying for, and you would not know until you tried to restore and found a hole where your photos used to be.
So treat DO snapshots and the add-on as a fast rebuild button for the operating system and config, nice to have, genuinely useful for getting a broken droplet breathing again. Then run a real, app-consistent, offsite backup of your actual data on top, because that is the copy that saves you when DO itself is the problem. Snapshots are convenience. Your offsite pull is insurance. Building a droplet into your self-hosted backup strategy means accepting that the rebuild button and the recovery copy are two different jobs.
The offsite layer of your self-hosted backup strategy: restic (and what it costs)
You have your dumps and your tarballs. Now you need something to encrypt them, dedupe them, and ship them offsite on a schedule without you babysitting it. That tool is restic, and it is the layer that finally satisfies the “two media plus one offsite” part of your self-hosted backup strategy.
restic encrypts everything client-side before it leaves your machine, dedupes so you are not re-uploading unchanged data every night, does incremental snapshots, and speaks Backblaze B2, S3, plain SSH, and more. Borg is the well-loved alternative if your offsite target is your own SSH box, though it does not speak cloud object storage natively the way restic does. For a homelabber pointing at B2, I reach for restic.
Why not just rclone a folder to the cloud?
People ask why not just rclone a folder to the cloud and skip the new tool. You can, and plenty of people do for a while. The problem is that a naive rclone copy is not versioned, so the moment your live data gets corrupted or encrypted by something nasty, your script faithfully copies the corruption over your only offsite copy. You backed up the disaster.
restic keeps history. You can restore last Tuesday, not just last night. It checks integrity. It deduplicates so a 200GB library does not cost you 200GB of new upload every run. Could you script all of that around rclone yourself? Sure, and then you own a fragile pile of bash that breaks the one night you need it. Buy a steak instead of raising a cow.
Here is the shape of a restic-to-B2 job.
# point restic at a Backblaze B2 bucket
export RESTIC_REPOSITORY="b2:my-homelab-backups"
export RESTIC_PASSWORD="your-long-random-restic-password"
export B2_ACCOUNT_ID="your-b2-key-id"
export B2_ACCOUNT_KEY="your-b2-application-key"
# one-time: initialize the encrypted repo
restic init
# nightly: back up your dumps + tarballs folder
restic backup /srv/backups
# prune old snapshots, keep a sane history
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
One thing restic cannot save you from: losing the RESTIC_PASSWORD. It encrypts client-side, which is exactly what you want, and it means there is no “forgot password” link. No password, no restore, ever. So that password goes in the same safe place as your Home Assistant emergency kit and your n8n key. Notice we are back to keys again. The keys are always the part people lose.
The real monthly bill (a number, not a shrug)
Most guides wave at “the cloud” and leave the cost as a scary question mark, so people either over-build a second NAS they did not need or skip offsite entirely. Here is the actual number. Backblaze B2 runs about $6.95 per terabyte per month (it ticked up from $6 back in May 2026).
For a typical one-person homelab, after restic’s deduplication and compression, you are realistically storing a few dozen gigabytes of the stuff that actually matters: your databases, your configs, your Vaultwarden vault, your irreplaceable photos. That lands most people somewhere around $1 to $2 a month. Two bucks. That is the price of not losing twelve years of photos. Stop agonizing and turn it on.
The step everyone skips: actually restoring it
Everything above gets you backups. This section is the difference between having backups and having recovery, and they are not the same thing. The whole point of a self-hosted backup strategy is the recovery, not the warm feeling of a full drive. A backup you have never restored is a lottery ticket you have never scratched. You are pretty sure it is a winner. You have no actual evidence.
Restore into a throwaway target, not over your live data
The number one rule of test restores: never restore over your live data to “see if it works.” If the backup is bad, you just destroyed the working copy to test the broken one. That is how a fire drill becomes the actual fire.
Restore into a NEW, throwaway location instead. Pull the snapshot down, drop it into a fresh volume, point a clean test container at it, and confirm the app actually reads it. For an Immich restore that means loading the pg_dump into a fresh Postgres and checking the library mounts and the photos render. For Vaultwarden it means the test instance unlocks with your master password, which proves the vault data restored intact. (The rsa_key files are what keep your existing sessions and pending invites valid, so they belong in the backup too, even though the unlock itself rides on your master password.) The verification is the whole point: an app that boots and shows your real data is a backup. An archive that exists is a hope.
# restic restores into a SEPARATE path, never over live data
restic restore latest --target /tmp/restore-test
# spin up a throwaway DB and load the dump to confirm it opens
gunzip -c immich-db-2026-06-18.sql.gz \
| docker exec -i test_postgres psql --username=postgres
If that test instance opens, reads your data, and does not throw errors, congratulations, you have a real backup. If it chokes, you just found out on a Tuesday afternoon with your real data safe, instead of at 2 a.m. on the worst day of your year.
A cadence you’ll actually keep (quarterly + monthly)
Perfect is the enemy of done here, so build a cadence you will actually maintain instead of a heroic one you will abandon by March.
Monthly: a quick integrity check and a glance at the logs. restic check verifies the repo is not rotting, and reading the backup logs catches the silent failure where your job has been erroring out for three weeks and you never noticed. Backups fail quietly. That is their favorite trick.
Quarterly: a full test restore into a throwaway target, the real thing from the section above, for at least your most painful-to-lose service. Four times a year. Put it on the calendar like a dentist appointment, because skipping it has the same kind of consequence, just more expensive.
Is that gold-plated? No. A bank would do more. But for a one-person homelab, a monthly check plus a quarterly real restore is the line between “I have a self-hosted backup strategy” and “I have a folder I am superstitious about.” Cross that line and you can actually sleep.
FAQ: the questions that come up the day after a scare
Can I just back up the whole Docker folder and be done?
For services without a database, basically yes, as long as you stop the container first so you are not copying mid-write. For Immich, Vaultwarden, or n8n, no. Their databases need a proper dump (or a stopped container), and you must also grab the encryption keys. Re-read the per-service playbook above. The keys are the part that gets people.
Do DigitalOcean snapshots count as my offsite backup?
No. Snapshots and the DO backup add-on live in the same datacenter as your droplet, and the add-on skips attached block-storage volumes entirely. They are a great fast-rebuild button for the OS. They are not your offsite copy. Pull the real app data to Backblaze B2 or somewhere outside DO for the leg that actually saves you.
How much does offsite backup really cost?
About $6.95 per terabyte per month on Backblaze B2, which for a deduplicated one-person homelab usually works out to $1 to $2 a month. It is genuinely cheaper than the coffee you will need on the morning you discover you skipped it.
What happens if I lose the encryption key?
Depends which key. Home Assistant’s emergency kit, n8n’s N8N_ENCRYPTION_KEY, and your restic password all decrypt your data, so losing one means the data is gone, full stop. A backup you cannot decrypt is not a backup, it is an expensive paperweight in the cloud. Vaultwarden is the exception: its vault is encrypted from your master password, so you recover it with that even if the rsa_key files go missing. Those keys keep your sessions and invites valid rather than decrypting the vault, so save them too, just know they are not the thing standing between you and your passwords. Store every key off the server it protects.
Your turn
Now go look at your stack and answer one honest question: if the disk under your Immich library died right now, this second, could you get it all back? Not “do I have backups.” Have you ever actually restored one?
If the answer is no, you are not alone, and that is the entire reason this post exists. Pick one service today. Just one. Do the dump, ship it to B2 with restic, then do a test restore into a throwaway target so you KNOW it works. Then move to the next. A self-hosted backup strategy you have tested beats a perfect one you have only imagined.
Tell me in the comments what is in your stack and whether you have ever done a real test restore. I want the confession: which service would gut you to lose, and did it take a near-disaster to finally make you set this up? If this saved you from a bad weekend, share it with the friend whose homelab you know is running on hope. And if you have not built these services yet, the setup guides linked up top will get you there first. Just promise me you will back them up this time. 🔒
Sources
- Backblaze, “The 3-2-1 Backup Strategy”: https://www.backblaze.com/blog/the-3-2-1-backup-strategy/
- Immich official documentation, “Backup and Restore”: https://docs.immich.app/administration/backup-and-restore/
- PostgreSQL Documentation, “File System Level Backup” (why a raw copy of a running server is unsafe): https://www.postgresql.org/docs/current/backup-file.html
- PostgreSQL Documentation, “pg_dump”: https://www.postgresql.org/docs/current/app-pgdump.html
- augmentedmind.de, “Backup Docker volumes (and restore them), done right”: https://www.augmentedmind.de/2023/08/20/backup-docker-volumes/
- HomeLab Starter, “Restic: Modern Backup for the Homelab”: https://homelabstarter.com/homelab-restic-backup-guide/
- nXsi, “Backblaze B2 Setup for Homelab Off-Site Backups” (offsite cost reference): https://www.nxsi.io/guides/backblaze-b2
- SimpleBackups, “DigitalOcean Snapshots vs. Backups”: https://simplebackups.com/blog/digitalocean-snapshots-vs-backups
- Akash Rajpurohit, “Self-Host Vaultwarden with Scheduled Backups” (Vaultwarden RSA keys): https://akashrajpurohit.com/blog/selfhost-vaultwarden-with-scheduled-backups/
- Home Assistant documentation, “Backups”: https://www.home-assistant.io/common-tasks/general/#backups
