Backups with restic

← 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

Time: 1 hour · Difficulty: Medium

Step 1: Decide What to Back Up

Back upWhy
~/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 dataIrreplaceable personal files.
Music / films / audiobooksReplaceable 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

What's Next?

You now have a lot of apps. Give them one home page:

Foundations 6: A Dashboard with Homepage →