CMS Database Backup and Restore: A Practical Guide (2026)
mysqldump, cron, the 3-2-1 rule, and restores you've actually tested
Most people learn the value of a database backup the exact moment they don't have one. A dropped table, a bad migration, a hacked plugin, a disk that quietly dies at 3 a.m. — and suddenly every post, comment, and user account is gone with no way back.
TL;DR: A solid CMS backup means dumping your database on a schedule (mysqldump or sqlite3 .backup), storing copies in at least two places off the server, automating it with cron, and — this is the part everyone skips — actually testing that you can restore. Follow the 3-2-1 rule and you'll survive almost any failure.
This guide covers the practical stuff for anyone self-hosting a CMS: how to back up MySQL and SQLite, how to automate it, and how to restore fast when things break. Backups are boring right up until they're the only thing standing between you and a rebuilt-from-scratch site. If you're just setting up your stack, our self-hosted CMS backup strategy walkthrough goes deeper on tooling and retention.
What counts as a "CMS backup" anyway?
A real CMS backup is two things: the database (your posts, users, settings, metadata) and the files (uploaded media, config, .env). Miss either one and a restore leaves you with holes. This guide focuses on the database, since that's the piece people most often get wrong.
Your CMS keeps almost everything relational in the database. WordPress uses MySQL. Ghost uses MySQL or SQLite. Statamic can run flat-file or database. A Laravel-based CMS like UnfoldCMS runs on MySQL in production and SQLite for smaller installs. The engine matters because the backup command is different for each — but the strategy around it is the same.
Uploaded images and PDFs usually live on disk (or object storage), not in the database. So a complete backup plan pairs a database dump with a file sync. Keep that split in your head: two data types, two backup methods, one restore that stitches them back together.
How do you back up a MySQL database?
Short answer: Use mysqldump to write the whole database to a single SQL file, compress it, and copy it somewhere off the server. One command gives you a portable snapshot you can restore anywhere MySQL runs.
The basic dump:
mysqldump -u root -p --single-transaction --quick \
--routines --triggers mydb > mydb-2026-07-03.sql
Two flags matter. --single-transaction takes a consistent snapshot without locking your tables, so the site stays up while the dump runs (works on InnoDB, the default engine). --quick streams rows instead of buffering them all in memory, which keeps big databases from blowing up RAM.
Compress it right away — SQL dumps are mostly text and shrink hard:
mysqldump -u root -p --single-transaction --quick mydb | gzip > mydb-2026-07-03.sql.gz
A 500 MB database often lands under 50 MB gzipped. That's cheaper to store and faster to move offsite.
For larger sites, look at mydumper (parallel dumps) or Percona XtraBackup (physical, near-instant for huge datasets). But for most self-hosted CMS installs under a few gigabytes, plain mysqldump on a cron job is all you need.
How do you back up SQLite?
Short answer: Don't just copy the .sqlite file while the app is running — a write mid-copy corrupts it. Use the built-in .backup command, which safely snapshots a live database.
SQLite stores your whole database in one file, which makes people think a cp is enough. It isn't. If the CMS writes during the copy, you get a torn file. Use this instead:
sqlite3 database.sqlite ".backup 'backup-2026-07-03.sqlite'"
This locks correctly, waits for in-flight writes, and produces a clean copy even under load. Then gzip and ship it offsite like any other backup:
gzip backup-2026-07-03.sqlite
If your CMS uses WAL mode (write-ahead logging, common in Laravel apps), the .backup command still handles it correctly — it folds the WAL into the snapshot. A raw file copy would miss recent writes sitting in the -wal sidecar file.
How often should you back up a CMS?
Short answer: Match backup frequency to how much data you can afford to lose. A busy site with daily posts and comments wants hourly or daily dumps. A brochure site that changes monthly can back up weekly. The question isn't "how often" — it's "how many hours of lost work is acceptable?"
That acceptable-loss window is your RPO (recovery point objective). Pick it first, then set your schedule to match.
| Site type | Change rate | Backup frequency | Retention |
|---|---|---|---|
| Active blog / news | Many writes daily | Hourly or daily | 30 days |
| Small business site | Weekly edits | Daily | 14 days |
| Docs / brochure | Monthly edits | Weekly | 8 weeks |
| E-commerce / user data | Constant writes | Hourly + binlog | 90 days |
Retention matters as much as frequency. If corruption sneaks in on Monday and you don't notice until Friday, a single overwritten backup file means every copy you have is already poisoned. Keep a rolling window — daily for a month, weekly for a quarter — so you can jump back past the moment things went wrong.
The 3-2-1 rule (and why offsite is non-negotiable)
Short answer: Keep 3 copies of your data, on 2 different types of storage, with 1 copy offsite. A backup sitting on the same server as your database is not a backup — it dies with the server.
This is the one rule to memorize. Play it out: your dump lives on the same VPS as MySQL. The disk fails, or the provider suspends the account, or someone runs rm -rf in the wrong directory. Database and backup vanish together. You have nothing.
Offsite means a different machine you don't control the same way — S3, Backblaze B2, a home NAS, another provider. Cheap object storage runs a few cents per GB per month, so there's no real excuse. Push your gzipped dump there right after it's created:
# after the dump, copy offsite
aws s3 cp mydb-2026-07-03.sql.gz s3://my-backups/db/
# or rclone to Backblaze / Google Drive / anywhere
rclone copy mydb-2026-07-03.sql.gz b2:my-backups/db/
Hardening the server itself is part of the same job — a backup won't help if an attacker also wiped your offsite bucket using leaked keys. Our practical guide to securing a self-hosted CMS covers locking down credentials and access.
Automating it with cron
Short answer: A manual backup you have to remember is a backup you'll forget. Wire a dump script into cron so it runs on its own, then have it delete copies older than your retention window.
Put the dump, compress, upload, and cleanup into one script:
#!/bin/bash
DATE=$(date +%F-%H%M)
DEST=/var/backups/cms
mysqldump -u backup -p"$DB_PASS" --single-transaction --quick mydb \
| gzip > "$DEST/mydb-$DATE.sql.gz"
# push offsite
rclone copy "$DEST/mydb-$DATE.sql.gz" b2:my-backups/db/
# delete local dumps older than 14 days
find "$DEST" -name "*.sql.gz" -mtime +14 -delete
Then schedule it. This runs a backup every day at 2:30 a.m.:
30 2 * * * /usr/local/bin/backup-cms.sh >> /var/log/cms-backup.log 2>&1
Log the output and check it once in a while. A cron job that's been silently failing for three weeks is worse than no cron job — it gives you false confidence. Bonus points: pipe a success ping to a monitor like Healthchecks.io so you get an alert when a backup doesn't run.
Full vs incremental backups
Short answer: A full backup copies everything every time — simple, big, slow. An incremental copies only what changed since the last backup — small and fast, but restore needs the full plus every increment in order. Most CMS sites should just run full dumps; they're simpler and small enough not to matter.
| Full backup | Incremental backup | |
|---|---|---|
| What it copies | Entire database | Only changes since last backup |
| Backup size | Large | Small |
| Backup speed | Slower | Fast |
| Restore speed | Fast (one file) | Slower (chain of files) |
| Complexity | Low | Higher (needs binlog / tooling) |
| Best for | Most self-hosted CMS sites | Very large / high-write databases |
For MySQL, true incrementals use binary logs (binlog) — you take a periodic full dump plus continuous binlog capture, letting you restore to any point in time. That's powerful for high-write sites but adds real complexity. Unless you're running something transaction-heavy, a nightly full dump plus decent retention gives you 95% of the safety for 20% of the effort.
Testing your restore (the step everyone skips)
Short answer: An untested backup is a hope, not a plan. Once a month, restore your latest dump into a throwaway database and confirm it works. Half of all "backup failures" are actually restore failures discovered too late.
Here's a numbered drill you can run in ten minutes:
- Spin up a scratch database:
mysql -u root -p -e "CREATE DATABASE restore_test;" - Pull your latest offsite dump and unzip it:
gunzip -k mydb-latest.sql.gz - Import it:
mysql -u root -p restore_test < mydb-latest.sql - Check row counts against production:
SELECT COUNT(*) FROM posts;on both. - Point a local copy of the CMS at
restore_testand load the homepage. - Confirm images and recent posts show up, then drop the scratch DB.
If step 3 throws an error, you just learned — safely — that your backup is broken, instead of finding out during a real outage. Write down how long the whole restore took. That number is your RTO (recovery time objective), and it's the honest answer to "how long until we're back up?"
What to do about it
If you're self-hosting a CMS right now and can't point to a backup from the last 24 hours, here's the order to fix it:
- Run a manual dump today. Don't wait for the perfect script —
mysqldump(orsqlite3 .backup) once, right now, gets you a floor. - Get one copy offsite. Upload today's dump to S3, B2, or even a personal cloud drive. Same-server backups don't count.
- Automate with cron. Drop the script above in place, set it to your RPO, and log the output.
- Add retention + cleanup. Keep a rolling window; let old dumps expire so you don't fill the disk.
- Test the restore this week. Run the six-step drill. Put a monthly reminder on the calendar.
- Back up files too.
rsyncyour uploads/media directory offsite alongside the database.
Do those six and you've moved from "one bad night away from disaster" to "annoying but recoverable." That's the whole game.
Where UnfoldCMS fits
If you'd rather not hand-roll cron scripts, some CMS platforms bundle backups. UnfoldCMS — a self-hosted Laravel 12 and React 19 CMS — includes a Backups module in its Pro tier built on Spatie's Laravel Backup package. From /admin/system/backups you can create, download, and delete backups that bundle both the database and files into a single archive, without touching the command line. It works with MySQL and SQLite.
It's not magic — under the hood it's still mysqldump and a file copy, the same mechanics this guide covers. The value is that it's wired into the admin UI and can be scheduled without editing crontab by hand. You still own the decision of where copies go offsite, and you still need to test restores. If keeping your data on infrastructure you control is the point, our take on data ownership and your CMS explains why that matters. Backups sit in the Pro tier — see the features page for what's included.
FAQ
Do I need to back up files if my media is on S3? Object storage like S3 is durable, but durable isn't the same as backed up. A bad deploy or a leaked key can still delete objects. Enable versioning on the bucket or sync a copy elsewhere. Durability protects against hardware failure, not against you.
Can I just use my host's automatic backups? They're a fine safety net but a bad primary plan. Host snapshots often live in the same account and region, restore the whole VPS (not just one database), and can be hard to test. Keep them, but run your own independent dumps offsite too.
How long does a restore actually take? For a dump under a gigabyte, importing back into MySQL usually takes a few minutes. The slow part is often finding a good backup and getting it onto the server. That's exactly why you time your restore drill — so you know your real RTO before you need it.
Is mysqldump safe to run on a live production site?
Yes, with --single-transaction on InnoDB tables. It takes a consistent snapshot without locking writes, so visitors won't notice. Without that flag, mysqldump can lock tables and briefly freeze the site on a busy database.
What's the difference between a backup and replication? Replication keeps a live copy of your database in sync in real time — great for failover, useless against human error. If you delete a table, replication faithfully deletes it on the replica too. Backups are point-in-time snapshots you can roll back to. You want both for anything serious.
Getting started
If you're standing up a new self-hosted install, bake backups in from day one rather than bolting them on after a scare. Our step-by-step guide to setting up a self-hosted CMS includes the server basics, and the pricing page shows which tier ships the built-in Backups module. Whichever CMS you run, the rule holds: dump on a schedule, store offsite, test the restore.
Methodology: commands in this guide reflect standard MySQL 8 and SQLite 3 tooling and the 3-2-1 backup rule as commonly applied to self-hosted web applications in 2026. Test any script against a scratch database before trusting it in production. UnfoldCMS feature claims reflect the Pro-tier Backups module (Spatie Laravel Backup) as shipped at time of writing.
Free & Open Source
Own your CMS. No subscriptions.
Unfold CMS is free to download and self-host. Built on Laravel + React, full source code included.
Share this post: