Chapter 5
Chapter 5 — Backup and Restore
Hardware fails. People run DELETE without a WHERE clause. Ransomware encrypts /var/lib/mysql. Migrations go wrong. All of these happen, and all of them feel impossible until the moment they happen to you. A working backup and restore process is the single most important operational habit you can build around a database — and it takes less than an hour to set up properly.
--single-transaction is critical for InnoDB). Creating a dedicated backup user with minimal privileges. Credentials files — keeping passwords out of your shell history. Gzip-compressed timestamped dumps. Inspecting and verifying a dump file. Restoring from a dump — single database, all databases, and renaming during restore. A complete automated backup script with rotation. Cron scheduling. Offsite copies with rclone. Testing restores. Binary log and point-in-time recovery basics. Troubleshooting.
Before We Start: The 3-2-1 Rule
The 3-2-1 rule is the minimum viable backup strategy: 3 copies of your data, on 2 different media types, with 1 copy offsite. For a home server this translates to: a daily dump on the server itself, a second copy on an external drive or NAS, and a third copy in cloud storage.
rm -rf, or a corrupted RAID volume takes both the database and the backup simultaneously. The backup script in this chapter saves locally and syncs offsite.
Equally important: an untested backup is not a backup. A dump file that can't be restored is worthless. Once your automated backup is running, schedule a monthly test restore to a temporary database. The instructions for this are at the end of the chapter.
What mysqldump Produces
mysqldump connects to MySQL and generates a SQL script that, when executed, recreates the database from scratch — schema and data. The output is a plain text file of SQL statements: DROP TABLE IF EXISTS, CREATE TABLE, then batched INSERT statements for the data.
Creating a Dedicated Backup User
Don't run backups as root. Create a minimal backup account with only the privileges mysqldump actually needs.
Credentials File — Never Put Passwords in the Command Line
Running mysqldump -u backup_user -pYourPassword bookshop > dump.sql exposes the password in your shell history, in ps aux output, and potentially in cron logs. The correct approach is a credentials file.
The Flags That Matter
| Flag | What it does | Use? |
|---|---|---|
| --single-transaction | Takes a consistent snapshot of InnoDB tables using a transaction, avoiding table locks. Allows reads and writes to continue during the dump. | Always for InnoDB |
| --routines (-R) | Include stored procedures and functions in the dump. Not included by default. | Yes, if you use them |
| --events (-E) | Include scheduled events. Not included by default. | Yes, if you use them |
| --triggers | Include triggers. Included by default — shown here for clarity. | Default on — no action needed |
| --databases db1 db2 | Dump multiple named databases. Includes CREATE DATABASE statements, so restore creates the database automatically. | For multi-db dumps |
| --all-databases (-A) | Dump every database, including mysql (user accounts). Good for full server backups. | For full server backup |
| --no-tablespaces | Omits tablespace CREATE statements. Required if your backup user doesn't have PROCESS privilege and you encounter INFORMATION_SCHEMA errors. | Add if you see errors |
| --set-gtid-purged=OFF | Suppress GTID comments in the dump. Needed when restoring between servers that use different GTID configurations. | Add if using replication |
| --lock-tables | Locks tables one by one before dumping. For MyISAM tables only. Blocks all writes during the dump. | Avoid — use --single-transaction instead |
Running mysqldump
Restoring from a Dump
Restoring is the reverse operation — you pipe the SQL file back into the mysql client. The database must exist before you restore a single-database dump (one not made with --databases); it's created automatically for dumps made with --databases or --all-databases.
DROP TABLE IF EXISTS before each CREATE TABLE, restoring will DROP and recreate every table in the dump — losing any data added after the backup was taken. If you need to keep current data, restore to a temporary database name first, then selectively move rows.
The Automated Backup Script
minute hour day month weekday command. 0 2 * * * = minute 0, hour 2 (2 AM), every day.
Offsite Copies with rclone
Local backups protect against accidental deletion and software errors. An offsite copy protects against hardware failure, fire, theft, and ransomware. rclone can sync your backup directory to any S3-compatible storage, Backblaze B2, Google Drive, Dropbox, or a remote SSH server.
Add the rclone sync to your backup script, after the local dumps complete:
Testing Your Backups — The Restore Test
Once a month, restore your most recent backup to a test database and verify the data looks correct. This confirms the dump is valid and the restore procedure works — before you actually need it in an emergency.
Binary Log and Point-in-Time Recovery
A mysqldump captures the database as it was at the moment of the dump. Binary logs record every change made after that point. Together they enable point-in-time recovery (PITR): restore a Monday backup, then replay Wednesday morning's binlog events up to the exact moment before someone ran DELETE FROM orders; without a WHERE clause.
--master-data=2 (MySQL 5.7) or --source-data=2 (MySQL 8.0) to your mysqldump command — it adds a comment near the top of the dump file showing exactly which binlog file and position the dump corresponds to. This is the starting point for PITR.
Backup Tool Comparison
util.dumpInstance() or util.dumpSchemas(). Requires MySQL Shell to be installed. Not a SQL file — uses a proprietary format (load with util.loadDump()).Recommended Backup Strategy for a Home Server
Troubleshooting
--column-statistics=0 to the mysqldump command. Or ensure the mysql client matches the server version: mysql --version should match mysqld --version.
SHOW PROCESSLIST; — look for rows with a long-running query in the State column. If another process is holding a lock, KILL <id>; it. Long-term fix: always use --single-transaction for InnoDB tables — it uses a snapshot and doesn't need table locks. If the hang is on a MyISAM table, schedule the backup when traffic is low.
--databases or --all-databases, so it includes a CREATE DATABASE statement — and that database already exists. Either drop it first (DROP DATABASE bookshop; — careful, data loss!) or add --force to the mysql restore command to skip errors. If you want to restore to the same database without dropping it first, use a single-database dump (without --databases) and specify the database name in the restore command.
df -h /var/backups), mysqldump lost the connection partway through, or the backup user lacked privileges on a table. Check: sudo mysqldump --defaults-file=/etc/mysql/backup.cnf bookshop 2>&1 | tail -20 — run without gzip and pipe to tail to see any error messages. Fix the root cause, then rerun. Add set -e to your backup script to make it exit on any error.
mysqldump if it's not on the default PATH. Fix by using the full path in the cron job or script: /usr/bin/mysqldump instead of just mysqldump. Also confirm the cron job is running as root (use sudo crontab -e to edit root's crontab, not your user's). Check the cron log: grep mysql_backup /var/log/syslog.
Quick Reference — Chapter 5
| Command | Purpose |
|---|---|
| mysqldump --defaults-file=/etc/mysql/backup.cnf --single-transaction --routines --events db | gzip > db.sql.gz | Full production-quality dump of one database, compressed |
| mysqldump ... --all-databases | gzip > all.sql.gz | Full server backup including all databases and user accounts |
| zcat db.sql.gz | tail -3 | Verify the dump completed — look for "Dump completed on" in the last lines |
| zcat db.sql.gz | sudo mysql dbname | Restore a compressed single-database dump (database must already exist) |
| sudo mysql -e "CREATE DATABASE dbname CHARACTER SET utf8mb4;" | Create the target database before restoring a single-database dump |
| find /var/backups/mysql -name "*.sql.gz" -mtime +7 -delete | Delete backups older than 7 days |
| rclone copy /var/backups/mysql b2:bucket/mysql/ | Sync local backups to Backblaze B2 (or any rclone remote) |
| sudo crontab -e | Edit root's crontab to schedule the backup script |
| SHOW BINARY LOGS; | List binary log files on the server (needed for PITR) |
| mysqlbinlog --stop-position=N binlog.000002 | sudo mysql db | Replay binlog events up to a specific position (point-in-time recovery) |
| tail -f /var/log/mysql_backup.log | Monitor backup job output |