SELFHOST.COMPUTER

Back up your VPS with restic

#backups#restic#self-hosting

Every self-hoster loses data once. Mine was a Docker volume I deleted with docker compose down -v because I thought it would only remove the containers. After that I set up restic, and it's been running quietly every night since.

restic is a single binary. It encrypts everything before it leaves the server, only uploads what changed since last time, and can restore one file or the whole lot. Setup takes about twenty minutes.

Where do the backups go?

Not the same server. If the disk dies or you get locked out of the account, your backups go with it. Use one of these:

  • A second small VPS at a different provider or location. restic talks to it over SFTP, so all it needs is SSH and some disk space.
  • S3-compatible storage like Backblaze B2, Wasabi or Cloudflare R2. Cheap, and nothing to maintain.

I'll use SFTP here because it needs nothing beyond a second box you can SSH into.

Install and connect

restic needs to read everything, so it runs as root. Open a root shell for the rest of this post:

sudo -i
apt install restic

Root needs an SSH key that can log into the backup box:

ssh-keygen -t ed25519 -f /root/.ssh/backup -N ""
ssh-copy-id -i /root/.ssh/backup.pub backup@BACKUP_HOST

Then tell SSH to use that key for that host, in /root/.ssh/config:

Host BACKUP_HOST
    User backup
    IdentityFile /root/.ssh/backup

Create the repository

openssl rand -base64 32 > /root/.restic-pass
chmod 600 /root/.restic-pass

export RESTIC_REPOSITORY=sftp:BACKUP_HOST:/srv/restic/myvps
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic init

Copy that password somewhere that isn't the server, like your password manager. Without it the backups are useless random bytes. That's the point of the encryption, and also the trap.

Databases need a dump first

Copying the files of a running Postgres or MySQL can give you a broken backup. Dump them to a file first and back up the dump:

docker compose exec -T db pg_dumpall -U postgres > /srv/dumps/postgres.sql

SQLite (Vaultwarden, Uptime Kuma and plenty of others use it) has its own safe copy command:

sqlite3 /path/to/db.sqlite3 ".backup '/srv/dumps/vaultwarden.sqlite3'"

The backup script

Save this as /usr/local/bin/backup.sh:

#!/bin/sh
set -e
export RESTIC_REPOSITORY=sftp:BACKUP_HOST:/srv/restic/myvps
export RESTIC_PASSWORD_FILE=/root/.restic-pass

# dump databases first (delete this if you don't run Postgres,
# and change /srv/stack to wherever your compose.yaml lives)
mkdir -p /srv/dumps
cd /srv/stack && docker compose exec -T db pg_dumpall -U postgres > /srv/dumps/postgres.sql

restic backup /etc /srv /root --exclude-caches
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
chmod 700 /usr/local/bin/backup.sh
crontab -e
30 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

That keeps a week of dailies, a month of weeklies and half a year of monthlies. Because restic only stores each piece of data once, all of those snapshots together take up surprisingly little space.

Test the restore. Really.

Run the script by hand once, then try to get something back:

restic snapshots
restic restore latest --target /tmp/restore-test --include /etc/hostname
cat /tmp/restore-test/etc/hostname

If that prints your hostname, you have a working backup. Run restic check every month or so too. It verifies the repository isn't damaged. I put a reminder in my calendar, because nobody remembers this on their own.

< all posts · netcup voucher codes

Keep reading