← Self-Hosting · Foundations 5 of 8 · Replaces: Google Drive / iCloud / OneDrive backup subscriptions
What You'll Build
Self-hosting means you are the IT department. A dead SSD without backups means lost photos and passwords, forever. This guide sets up restic: fast, encrypted backups that only store what changed since last time, running automatically every night.
The 3-2-1 rule
- 3 copies of your data (the original + 2 backups)
- on 2 different devices (e.g. the server's SSD + a USB drive)
- with 1 copy off-site (cloud storage, or a drive at a friend's or family member's house)
Time: 1 hour · Difficulty: Medium
Step 1: Decide What to Back Up
| Back up | Why |
|---|---|
~/stacks/ (every compose file, .env and app data/ folder) | Your apps' settings, users, and databases. Small but critical. |
| Photo library (Immich upload folder), documents (Paperless), Nextcloud data | Irreplaceable personal files. |
| Music / films / audiobooks | Replaceable but time-consuming — back up if you have space. |
Skip caches, thumbnails, and Docker images — they rebuild themselves.
Step 2: Install restic and Create a Repository
A repository is the encrypted folder restic stores backups in. We'll make one on a USB drive mounted at /mnt/backup (see the drive mounting steps).
sudo apt install restic -y
sudo restic self-update # get the newest version
# Store the repository password in a root-only file
sudo mkdir -p /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/password'
sudo chmod 600 /etc/restic/password
# Create the repository on the USB drive
sudo restic -r /mnt/backup/restic-repo --password-file /etc/restic/password init
Copy the password somewhere safe off the server (your password manager). Without it, the backups can never be decrypted — not even by you.
sudo cat /etc/restic/password
Step 3: The Backup Script
sudo nano /usr/local/bin/homeserver-backup.sh
#!/bin/bash
# ─────────────────────────────────────────────────────────
# homeserver-backup.sh — nightly backup of the home server
# 1. Asks database apps to write a clean dump to disk
# 2. Backs up ~/stacks and personal data with restic
# 3. Deletes old snapshots, keeping 7 daily / 4 weekly / 6 monthly
# 4. Checks a sample of the backup data for corruption
# ─────────────────────────────────────────────────────────
set -euo pipefail
export RESTIC_REPOSITORY="/mnt/backup/restic-repo"
export RESTIC_PASSWORD_FILE="/etc/restic/password"
STACKS="/home/yourname/stacks" # ← change to your username
EXTRA=( ) # ← add big data folders, e.g. "/mnt/data/photos"
# 1. Database dumps (a copy of a live database file can be corrupt; a dump is safe)
# Uncomment the lines for apps you've installed:
# docker exec immich_postgres pg_dumpall -U postgres > "$STACKS/immich/db-dump.sql"
# docker exec paperless-db-1 pg_dumpall -U paperless > "$STACKS/paperless/db-dump.sql"
# 2. Back up (--exclude skips things that rebuild themselves)
restic backup "$STACKS" "${EXTRA[@]}" \
--exclude "*/cache/*" --exclude "*/thumbs/*" --exclude "*/encoded-video/*" \
--tag nightly
# 3. Keep a sensible history, delete the rest
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
# 4. Verify 5% of the stored data each night
restic check --read-data-subset=5%
echo "Backup finished: $(date)"
sudo chmod +x /usr/local/bin/homeserver-backup.sh
sudo /usr/local/bin/homeserver-backup.sh # first run is slow; later runs are fast
Step 4: Run It Every Night (systemd Timer)
sudo nano /etc/systemd/system/homeserver-backup.service
[Unit]
Description=Nightly restic backup of the home server
[Service]
Type=oneshot
ExecStart=/usr/local/bin/homeserver-backup.sh
sudo nano /etc/systemd/system/homeserver-backup.timer
[Unit]
Description=Run the home server backup every night at 3am
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true # if the server was off at 3am, run at next boot
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now homeserver-backup.timer
systemctl list-timers | grep backup # shows the next run time
journalctl -u homeserver-backup.service -n 50 # read the last run's output
Step 5: The Off-Site Copy
restic can write straight to cloud storage. Backblaze B2 is inexpensive and restic supports it natively; any S3-compatible provider works the same way.
# Example: a second repository on Backblaze B2 (create a bucket + app key first)
export B2_ACCOUNT_ID="your-key-id"
export B2_ACCOUNT_KEY="your-application-key"
restic -r b2:your-bucket-name:homeserver --password-file /etc/restic/password init
# Copy snapshots from the local repo to the cloud repo (add this to the script)
restic -r b2:your-bucket-name:homeserver --password-file /etc/restic/password \
copy --from-repo /mnt/backup/restic-repo --from-password-file /etc/restic/password
Everything is encrypted before it leaves your server — the provider can't see your files.
Step 6: Test a Restore (Don't Skip This!)
A backup you've never restored is only a hope. Do this now, and again every few months:
# Shortcut so we don't repeat the options
R="sudo restic -r /mnt/backup/restic-repo --password-file /etc/restic/password"
$R snapshots # list backups
$R ls latest | head # peek inside the newest one
# Restore one app's folder to a scratch location and compare
$R restore latest --target /tmp/restore-test --include /home/yourname/stacks/vaultwarden
ls /tmp/restore-test/home/yourname/stacks/vaultwarden
To restore an app for real: stop it (docker compose down), restore its folder to the original location, and start it again.
Troubleshooting
- "unable to open config file … no such file" — the USB drive isn't mounted. Check
df -h /mnt/backupand your fstab entry. - "repository is already locked" — a previous run crashed. Make sure no backup is running, then
restic unlock. - Container name in the pg_dumpall line is wrong — find the real name with
docker ps --format '{{.Names}}'.
What's Next?
You now have a lot of apps. Give them one home page:
Foundations 6: A Dashboard with Homepage →