How to Back Up a Self-Hosted Blog (SQL Server)
A practical, SQL Server-specific backup plan for a self-hosted Inkwell blog: what to save, how to script and schedule backups, and how to test restores.
Running your own blog means you own the uptime, and you own the data. If a disk fails, a deploy goes wrong, or a bad migration corrupts a table, the only thing standing between you and a rebuild-from-memory nightmare is a backup you can actually restore. Inkwell stores everything in Microsoft SQL Server, so a good backup plan is really a SQL Server plan plus a couple of files. This guide walks through a complete strategy: what to save, how to script and schedule it, and how to prove it works. If you have not deployed yet, start with self-hosting a blog on a Linux VPS or the Docker and Caddy deploy guide, then come back here.
The 3-2-1 rule
The industry baseline is simple and worth internalising: keep 3 copies of your data, on 2 different media, with at least 1 copy offsite. For a small blog that might mean the live database, a nightly .bak on a separate volume, and a copy pushed to object storage in another region. The point is that no single failure, a dead drive, a ransomware event, a datacentre outage, can take out every copy at once.
What to back up
An Inkwell install has three things worth protecting. Miss one and your restore is incomplete:
- The SQL Server database holds posts, pages, subscribers, users, and site settings. This is the crown jewel and the main focus below.
- The media/uploads library holds images and files. Depending on your setup these live in SQL Server and the media library, so treat that storage as a first-class backup target alongside the database.
- appsettings.json holds your configuration: connection strings, mail settings, and keys. Keep an encrypted copy in a secrets manager or private repo, never in a public one.
How to back up SQL Server
SQL Server has a native, transactional backup command. A full backup to a .bak file looks like this:
BACKUP DATABASE [Inkwell]
TO DISK = N'/var/opt/mssql/backups/Inkwell_full.bak'
WITH FORMAT, INIT, COMPRESSION,
NAME = N'Inkwell full backup';
To run that non-interactively from a script, invoke it with sqlcmd. Add a date stamp so each run writes its own file:
STAMP=$(date +%F)
sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -Q \
"BACKUP DATABASE [Inkwell] TO DISK = N'/var/opt/mssql/backups/Inkwell_$STAMP.bak' WITH FORMAT, INIT, COMPRESSION;"
Scheduling depends on your edition. Full editions ship SQL Server Agent, so create an Agent job that runs the backup on a nightly schedule. SQL Server Express has no Agent, so drive the same sqlcmd command from an OS scheduler instead, a cron entry on Linux or a Scheduled Task on Windows:
# /etc/cron.d/inkwell-backup -- 02:30 every day
30 2 * * * root /usr/local/bin/inkwell-backup.sh >> /var/log/inkwell-backup.log 2>&1
Whichever path you take, also back up the media files with the same schedule using a tool like rsync or your storage provider's CLI, so images never drift out of sync with the database.
Offsite and encryption
A backup that sits on the same server as the database is not really a backup. After each run, copy the .bak offsite to object storage (S3-compatible, Azure Blob, Backblaze B2, or similar). Encrypt at rest: enable server-side encryption on the bucket, and for sensitive data encrypt the file before upload with a tool such as age or GPG. Because Inkwell has no telemetry and keeps everything self-hosted, your encrypted offsite copy stays fully under your control and GDPR-friendly.
A backup you have never restored is not a backup, it is a hope.
Test your restores
Untested backups fail silently, right when you need them. Schedule a periodic restore drill: pull the latest .bak and restore it into a scratch database, then confirm your post count and newest article look right.
RESTORE DATABASE [Inkwell_restore_test]
FROM DISK = N'/var/opt/mssql/backups/Inkwell_2026-07-11.bak'
WITH MOVE 'Inkwell' TO '/var/opt/mssql/data/Inkwell_test.mdf',
MOVE 'Inkwell_log' TO '/var/opt/mssql/data/Inkwell_test_ldf.ldf',
REPLACE;
Do this monthly, and after any major upgrade. Once you trust the process, you can rebuild your entire blog on fresh hardware in minutes. For connection string details and configuration, see the documentation.
Frequently asked questions
What database does Inkwell use, and can I use pg_dump?
Inkwell uses Microsoft SQL Server (2019 or later, or SQL Server LocalDB), not PostgreSQL or SQLite. Use the native BACKUP DATABASE command via sqlcmd rather than PostgreSQL tools like pg_dump.
How do I schedule backups on SQL Server Express?
SQL Server Express has no SQL Server Agent, so schedule the backup from the operating system instead. Put your sqlcmd BACKUP DATABASE command in a script and trigger it with a cron job on Linux or a Scheduled Task on Windows.
What exactly should I back up besides the database?
Back up three things: the SQL Server database (posts, subscribers, settings), the media and uploads library, and your appsettings.json configuration. Keeping only the database leaves images and connection settings unprotected.
How often should I test a restore?
Run a restore drill at least monthly and after any major upgrade. Restore the latest .bak into a scratch database and verify the data. A backup you have never restored is a guess, not a guarantee.
Ready to host your own blog?
Inkwell is free, open-source, and self-hosted — your content, your server, your rules. Deploy in minutes on .NET 10.