My Server Journal

Backing up a small server with restic

I wanted three things from backups: encrypted before they leave the machine, deduplicated so daily snapshots cost almost nothing, and a restore I have actually performed rather than assumed. restic does all three with one static binary.

Repository

The repository can live on anything restic can talk to — SFTP, S3-compatible storage, a local disk. I use an S3-compatible bucket because it is cheap and off the machine. Credentials and the repository password go in a root-only file so cron jobs can read them:

sudo install -m 600 /dev/null /root/.restic.env
sudo tee /root/.restic.env >/dev/null <<'EOF'
export RESTIC_REPOSITORY="s3:https://s3.example.net/my-bucket"
export RESTIC_PASSWORD="a long passphrase kept somewhere else too"
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
EOF
. /root/.restic.env && restic init

The passphrase is the whole ballgame: lose it and the repository is noise. It is written down in two places that are not this server.

What to back up

Not the whole disk. The operating system is reproducible; my data is not. For this box that means /etc, /home, /var/www, and a dump of any database, taken just before the snapshot:

#!/bin/bash
set -euo pipefail
. /root/.restic.env
restic backup /etc /home /var/www \
  --exclude-caches \
  --exclude '/home/*/.cache' \
  --tag daily
restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune

Saved as /usr/local/sbin/backup.sh (mode 700) and run from root's crontab at 03:40. The forget --prune line is what keeps the bucket from growing forever; the retention numbers are a guess I have not needed to revisit.

The part people skip

Once a month I restore something on purpose:

restic snapshots --last
restic restore latest --target /tmp/restore-test --include /etc/nginx
diff -r /etc/nginx /tmp/restore-test/etc/nginx && echo "restore OK"

And a few times a year, restic check --read-data-subset=10%, which actually reads a slice of the stored blobs instead of trusting the index. The first time I ran it I found a repository I had initialised with a typo in the bucket name — empty, and faithfully backed up to for two weeks.

Things I got wrong first