Confession time, sitting here on a Monday night with cold tea next to the keyboard: this blog has been running since November 2011 and until about ten days ago I did not have a real backup system for it. I had a backup system, in the loosest possible sense: every few months, whenever I remembered, I'd SSH in, tar up the database and the uploads folder, and drag the file onto my laptop over scp. Sometimes I remembered. A lot of the time I didn't, and I'd just tell myself the VPS provider surely had snapshots and I'd deal with it "later."
Later showed up two Fridays ago when a disk on the old box started throwing SMART errors at 2am and the host emailed me a very calm, very unhelpful notice about "degraded array health." Nothing died. But I stared at that email over breakfast and realized my most recent manual backup was from July. Fourteen years of posts, riding on vibes.
So here's what I actually did, because writing it down is basically the only way I'll keep doing it.
The setup
Nothing fancy. I didn't want a whole backup product with a dashboard and a login and its own subscription. I wanted three things: a nightly dump, compressed, copied somewhere that isn't the same box the site lives on. That's it.
The script itself is maybe fifteen lines:
#!/bin/bash
DATE=$(date +%F)
mysqldump -u techpad -p"$DBPASS" techpad_db | gzip > /home/backups/db-$DATE.sql.gz
tar -czf /home/backups/uploads-$DATE.tar.gz /var/www/techpad/uploads
find /home/backups -type f -mtime +14 -delete
The find line at the end matters more than it looks like it does — without it you just quietly fill your disk with two-week-old gzips until something else falls over. Ask me how I know, actually don't, I only just wrote this script.
Then one line in crontab, crontab -e, running it at 3:40am because that's a dead hour for traffic here:
40 3 * * * /home/scripts/backup.sh >> /home/backups/backup.log 2>&1
The part I'd skipped for fourteen years is the part that actually matters, which is getting the file off the server. A backup sitting next to the thing it's backing up is not a backup, it's a second copy of the same single point of failure. I set up a cheap rclone sync to a separate object storage bucket, run right after the tar step, so the nightly job dumps, compresses, and ships out before I've even had coffee.
Where it actually lives
I moved the site itself over to a new VPS a few months back through Tricknowtech, mostly because their git-push deploy meant I could stop hand-editing PHP over SFTP like it's 2013, which, no shade to 2013 me, but come on, and it turns out having hosting and the backup destination on genuinely separate infrastructure is the whole point. If the VPS provider has a bad night, the backups aren't having the same bad night.
I will say the boring stuff is the stuff that saves you. Nobody writes a blog post titled "I ran a cron job and nothing bad happened," but that's genuinely the goal here. The exciting version of this story is the one where I lose comments going back to 2012 because I was too lazy to write fifteen lines of bash.
A thing I got wrong the first time
My first pass at this only backed up the database. Took me an embarrassing amount of time to remember that half of what makes this blog this blog is the uploads folder — every screenshot, every dumb photo I took of a broken keyboard, every diagram I half-drew in MS Paint back when that was still my only image tool. A database with no media is just fourteen years of text with holes in it. So if you're doing this yourself, don't just dump the database and call it done. Check what directories actually hold your content before you decide the job is finished.
I tested the restore too, which people always skip. Spun up a throwaway droplet, pulled last night's dump down, ran it through mysql, pointed a local copy of the site at it. Took maybe twelve minutes start to finish and the whole archive loaded clean, including a post from 2014 with a broken image link that's apparently been broken for eleven years regardless of backups. Some things aren't worth fixing. But at least now I know I could lose the site and be back within the hour, instead of finding out the hard way at 2am on some future Friday.