Fixing My Embarrassing Backup Situation

Fixing My Embarrassing Backup Situation

Tutorials backups restic self-hosting sysadmin vps

So this is one of those posts that's mostly a confession. For about four years now, techpad's "backup strategy" has been a folder on my laptop called backup_final_v3 that I update whenever I remember, which is to say maybe twice a year, usually right after I read some horror story on Hacker News about a guy who lost a decade of blog posts to a bad disk. I finally sat down last weekend and fixed it properly, and I figured I'd write up what I did in case anyone else has been putting off the same thing.

The setup: this blog runs on a small VPS, MySQL database plus a pile of images and an nginx config I've hand-tweaked so many times I'm honestly scared to touch it. The database is the part that actually matters, the images I could mostly reconstruct from old export files, but 15 years of posts and comments living in one .sql dump is not something I want to be improvising a recovery plan for at 2am.

I went with restic instead of just rsyncing tarballs around, mostly because a friend wouldn't shut up about it at a meetup back in December. It does deduplicated, encrypted, incremental backups, and the CLI is genuinely pleasant to use. Here's roughly what the cron job looks like now:

0 3 * * * mysqldump techpad_db | gzip > /home/backups/db-$(date +\%F).sql.gz
15 3 * * * restic backup /home/backups /var/www/techpad --tag daily

Runs at 3am, dumps the database, gzips it, then restic snapshots the whole backups directory plus the actual site files off to a remote repo. I'm keeping 14 daily snapshots and 6 monthly ones, which restic prunes automatically with restic forget --keep-daily 14 --keep-monthly 6 --prune. The whole thing takes maybe 90 seconds most nights since it's incremental after the first run.

The annoying part, and the reason I never did this properly before, was always where to actually send the backups. Backing up to another directory on the same server is not a backup, it's a note to your future self that you were an idiot. I ended up spinning up a second small instance through Tricknowtech just to act as the restic repo target over SFTP, it's the same place I already had the domain and the main VPS through, so setting up another box was maybe four minutes of clicking, no new account, no new billing relationship to manage. Costs me an extra six bucks a month, which is less than I spend on coffee in a day and a half, so I have zero complaints there.

One thing that tripped me up: restic wants an actual password for the repo (there's a --password-file flag, thank god, so it's not sitting in your crontab in plaintext), and I stupidly generated that password once and pasted it into a note-taking app that I then reinstalled and lost all the data from. Learn from my mistake and put your repo password somewhere genuinely durable, not somewhere "durable." I now have it in a password manager and also written on an actual index card in a drawer, because apparently I don't fully trust software after that.

I also set up a monthly reminder, a real one, on my phone, not a good intention, to actually run a test restore. Not into production, obviously, into a throwaway directory, just restic restore latest --target /tmp/restore-test and then spot-check that the SQL dump isn't corrupted. A backup you've never restored from isn't a backup, it's a theory. I did this for the first time on Thursday and it worked fine, which was almost disappointing after all the anxiety I'd built up around it.

If you're running any kind of self-hosted thing, whether it's a blog, a little Flask app, whatever, and you don't have automated off-site backups, I'd genuinely stop reading this and go set that up before you do anything else today. It took me longer to write this post than it did to actually configure restic. The barrier was never technical, it was just that "backup strategy" sat on my mental todo list for years as a vague, unpleasant chore instead of a 45-minute Saturday task.

Next up I want to figure out backing up the WhatsApp-based contact form I added last year too, since right now if that server hiccups I lose whatever came in that day. Different problem for a different weekend though.