Tutorial

MySQL Backup and Restore: mysqldump, Automation, and Recovery

May 20, 2026

Back to Blog
Managing servers the hard way? Panelica gives you isolated hosting, built-in Docker and AI-assisted management.
Start free

The Importance of Database Backups

Your database is the most valuable asset on your server. Application code can be redeployed from version control, static files can be re-uploaded, but your database — containing user accounts, transactions, content, and years of accumulated data — is irreplaceable if lost. Every responsible server administrator needs a robust, tested, and automated backup strategy for MySQL databases.

The most common causes of data loss are not dramatic hardware failures. They are human errors: an accidental DROP TABLE, a botched migration, or a bad UPDATE without a WHERE clause. Having a recent, verified backup means the difference between a minor inconvenience and a catastrophic business event.

A Backup You Have Never Restored Is Not a Backup: It is a hope. This article covers not just how to create backups, but how to verify them and practice restoration. The worst time to discover your backup process is broken is during an actual emergency.

mysqldump: The Essential Tool

mysqldump is the standard logical backup tool included with every MySQL installation. It generates SQL statements that can recreate the database schema and data. While not the fastest backup method for very large databases, it is the most portable and universally compatible.

Basic mysqldump Syntax

# Backup a single database $ mysqldump -u root -p myapp_production > myapp_backup.sql # Backup all databases $ mysqldump -u root -p --all-databases > all_databases.sql # Backup specific tables from a database $ mysqldump -u root -p myapp_production users orders products > tables_backup.sql # Backup only the schema (no data) $ mysqldump -u root -p --no-data myapp_production > schema_only.sql # Backup only the data (no schema) $ mysqldump -u root -p --no-create-info myapp_production > data_only.sql

Essential mysqldump Flags

FlagPurposeWhen to Use
--single-transactionConsistent snapshot without locking (InnoDB)Always for InnoDB
--routinesInclude stored procedures and functionsAlways
--triggersInclude triggers (default: on)Always
--eventsInclude scheduled eventsIf you use events
--quickRetrieve rows one by one (less memory)For large databases
--lock-tablesLock tables during backupMyISAM tables only
--add-drop-databaseAdd DROP DATABASE before CREATEFull server restore
--set-gtid-purged=OFFOmit GTID statementsWhen restoring to different server
--column-statistics=0Disable column statistics queryMySQL 8.0+ with older dump
Why --single-transaction Matters: Without this flag, mysqldump locks tables during the backup, which blocks writes and can cause downtime. With --single-transaction, InnoDB creates a consistent snapshot at the start of the dump without any locking. This is essential for production backups. Note: this only works with InnoDB tables — MyISAM tables still require locking.

The Production-Ready mysqldump Command

# The recommended production backup command $ mysqldump -u root -p \ --single-transaction \ --routines \ --triggers \ --events \ --quick \ --set-gtid-purged=OFF \ myapp_production > myapp_$(date +%Y%m%d_%H%M%S).sql

Compression: Shrinking Backup Files

SQL dumps are plain text and compress extremely well. A 10GB dump might compress to 1-2GB with gzip or even smaller with zstd. Always compress backups to save storage space and reduce transfer times.

# Compress with gzip (most compatible) $ mysqldump -u root -p --single-transaction myapp | gzip > myapp_backup.sql.gz # Compress with zstd (faster, better ratio) $ mysqldump -u root -p --single-transaction myapp | zstd > myapp_backup.sql.zst # Compress with pigz (parallel gzip, uses all CPU cores) $ mysqldump -u root -p --single-transaction myapp | pigz > myapp_backup.sql.gz
ToolSpeedCompression RatioCPU UsageCompatibility
gzipModerate~70-80%Single coreUniversal
pigzFast~70-80%All coresgzip compatible
zstdVery fast~75-85%ConfigurableModern systems
xzSlow~85-90%HighMost systems

Encryption: Protecting Backup Data

Database backups contain your most sensitive data. If they are stored unencrypted and an attacker gains access to your backup storage, they have everything — user credentials, personal information, financial records. Always encrypt backups, especially those transferred offsite.

# Encrypt with OpenSSL (symmetric) $ mysqldump -u root -p --single-transaction myapp | gzip | \ openssl enc -aes-256-cbc -salt -pbkdf2 -out myapp_backup.sql.gz.enc # Decrypt $ openssl enc -d -aes-256-cbc -pbkdf2 -in myapp_backup.sql.gz.enc | \ gunzip > myapp_backup.sql # Encrypt with GPG (asymmetric — better for remote storage) $ mysqldump -u root -p --single-transaction myapp | gzip | \ gpg --encrypt --recipient [email protected] > myapp_backup.sql.gz.gpg # Decrypt with GPG $ gpg --decrypt myapp_backup.sql.gz.gpg | gunzip > myapp_backup.sql
Key Management: Your encryption key or GPG private key must be stored separately from your backups. If the key is on the same server and the server is compromised, encryption provides no protection. Use a password manager, hardware security module, or a separate secure location for keys.

Automated Backup Script with Cron

Manual backups are unreliable because they depend on someone remembering to run them. Automate your backups with a comprehensive shell script and schedule it with cron.

#!/bin/bash # /opt/scripts/mysql-backup.sh BACKUP_DIR="/opt/backups/mysql" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=30 MYSQL_USER="backup_user" MYSQL_PASS="secure_password" # Create backup directory mkdir -p "$BACKUP_DIR/daily" # Get list of all databases (exclude system DBs) DATABASES=$(mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW DATABASES;" | \ grep -Ev "(Database|information_schema|performance_schema|sys)") # Backup each database separately for DB in $DATABASES; do FILENAME="${BACKUP_DIR}/daily/${DB}_${DATE}.sql.gz" mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASS" \ --single-transaction --routines --triggers --events \ --quick "$DB" | gzip > "$FILENAME" if [ $? -eq 0 ]; then echo "[OK] $DB backed up: $(du -h $FILENAME | cut -f1)" else echo "[FAIL] $DB backup failed!" >&2 fi done # Delete backups older than retention period find "$BACKUP_DIR/daily" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete # Log summary echo "Backup complete: $(ls ${BACKUP_DIR}/daily/*_${DATE}* | wc -l) databases" echo "Total size: $(du -sh ${BACKUP_DIR}/daily/ | cut -f1)"
# Schedule with cron: daily at 3 AM $ crontab -e 0 3 * * * /opt/scripts/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1

Backup Rotation Strategy

Keeping every backup forever is wasteful. A rotation strategy balances recovery granularity with storage costs. The grandfather-father-son (GFS) approach is the most common:

Daily Backups
Keep 7 days
Weekly Backups
Keep 4 weeks
Monthly Backups
Keep 12 months
# Rotation script addition DOW=$(date +%u) # Day of week (1=Monday) DOM=$(date +%d) # Day of month # Weekly backup on Sundays if [ "$DOW" -eq 7 ]; then cp "$FILENAME" "${BACKUP_DIR}/weekly/" find "${BACKUP_DIR}/weekly" -mtime +28 -delete fi # Monthly backup on the 1st if [ "$DOM" -eq "01" ]; then cp "$FILENAME" "${BACKUP_DIR}/monthly/" find "${BACKUP_DIR}/monthly" -mtime +365 -delete fi

Restoring from a Backup

The restore process is straightforward but requires care. Always verify your backup before restoring to a production database.

Basic Restore

# Restore from uncompressed dump $ mysql -u root -p myapp_production < myapp_backup.sql # Restore from gzip compressed dump $ gunzip < myapp_backup.sql.gz | mysql -u root -p myapp_production # Restore from zstd compressed dump $ zstd -d < myapp_backup.sql.zst | mysql -u root -p myapp_production # Restore with progress (using pv) $ pv myapp_backup.sql.gz | gunzip | mysql -u root -p myapp_production 3.21GiB 0:04:23 [12.5MiB/s] [=============================>] 100%
Critical Warning: Restoring a backup replaces the existing data. If your backup includes DROP TABLE statements (which mysqldump adds by default), the current tables will be dropped and recreated. Always test restores on a separate database or server first, never directly on production.

Safe Restore Process

1
Create a test database: mysql -e "CREATE DATABASE myapp_restore_test;"
2
Restore to the test database: gunzip < backup.sql.gz | mysql myapp_restore_test
3
Verify data integrity: Compare row counts, check recent records, verify critical tables.
4
If verified, restore to production: Either rename databases or restore directly. Consider maintenance mode for your application during restore.
5
Clean up: Drop the test database after verification.

Handling Large Databases

For databases larger than a few gigabytes, standard mysqldump can be slow. Here are strategies for efficiently handling large databases:

Split by Table

# Backup each table to a separate file for TABLE in $(mysql -u root -p -N -e "SHOW TABLES FROM myapp"); do mysqldump -u root -p --single-transaction myapp "$TABLE" | \ gzip > "myapp_${TABLE}_$(date +%Y%m%d).sql.gz" done

Parallel Restore with GNU Parallel

# Restore multiple table dumps in parallel $ ls myapp_*.sql.gz | parallel -j4 \ 'gunzip < {} | mysql -u root -p myapp_production'

mysqlpump: Parallel Backup Alternative

# mysqlpump with parallel threads $ mysqlpump -u root -p --default-parallelism=4 \ --databases myapp_production | gzip > myapp_backup.sql.gz
ToolSpeed (Backup)Speed (Restore)ConsistencyBest For
mysqldumpModerateModerateExcellentMost use cases
mysqlpumpFast (parallel)ModerateGoodLarge databases
mydumper/myloaderVery fastVery fastExcellentTB-scale databases
xtrabackupFastestFastestExcellentHot backups, huge DBs

Binary Logs for Point-in-Time Recovery

What if you need to recover to a specific moment — for example, right before an accidental DELETE FROM users WHERE 1=1? This is where MySQL binary logs become invaluable. Binary logs record every data-changing statement, allowing you to replay transactions up to a precise timestamp.

# Enable binary logging in my.cnf [mysqld] log_bin = /var/log/mysql/mysql-bin binlog_expire_logs_seconds = 604800 # 7 days max_binlog_size = 100M binlog_format = ROW # List binary logs $ mysql -e "SHOW BINARY LOGS;" +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000045 | 104857600 | | mysql-bin.000046 | 52428800 | | mysql-bin.000047 | 12345678 | +------------------+-----------+ # Point-in-time recovery: replay logs up to a specific time $ mysqlbinlog --stop-datetime="2026-03-17 14:30:00" \ mysql-bin.000045 mysql-bin.000046 | mysql -u root -p
Point-in-Time Recovery Process: (1) Restore the most recent full backup. (2) Apply binary logs from the time of the backup to the moment before the disaster. This gives you maximum data recovery with minimal loss — typically measured in seconds, not hours.

Remote Backup Strategies

Local backups protect against accidental data loss but not against hardware failure or disasters. Remote backups are essential for true disaster recovery.

rsync to Remote Server

$ rsync -avz --progress \ /opt/backups/mysql/ \ backup@remote:/backups/mysql/

Upload to S3

$ aws s3 cp myapp_backup.sql.gz \ s3://my-backups/mysql/ \ --storage-class STANDARD_IA

Monitoring Backup Success

Automated backups can silently fail for weeks before anyone notices. Implement monitoring to verify backup completion, file sizes, and age.

# Check backup age and size LATEST=$(ls -t /opt/backups/mysql/daily/*.sql.gz | head -1) AGE_HOURS=$(( ($(date +%s) - $(stat -c %Y "$LATEST")) / 3600 )) SIZE=$(stat -c %s "$LATEST") if [ "$AGE_HOURS" -gt 25 ]; then echo "ALERT: Latest backup is ${AGE_HOURS}h old!" | \ mail -s "Backup Alert" [email protected] fi if [ "$SIZE" -lt 1000 ]; then echo "ALERT: Backup file suspiciously small: ${SIZE} bytes!" | \ mail -s "Backup Alert" [email protected] fi
  • Monitor backup age (alert if older than 25 hours)
  • Monitor backup size (alert if suspiciously small or zero)
  • Monitor backup count (alert if fewer backups than expected)
  • Periodically test restore to a staging database
  • Monitor available disk space on backup storage

How Panelica Manages Database Backups

Panelica provides automated MySQL database backups with scheduling, compression, and remote storage support. Through the panel, you can configure backup schedules, set retention policies, and store backups on remote destinations including Amazon S3, Google Drive, SFTP servers, and OneDrive.

  • Automated daily, weekly, and monthly backup schedules
  • Compression with configurable algorithms
  • Remote storage to S3, Google Drive, SFTP, and OneDrive
  • One-click restore directly from the panel
  • Per-database backup and selective table restore
  • Backup monitoring with email notifications on failure

The backup interface lets you see all available backups, their sizes, creation dates, and storage locations. Restoring any backup is a single-click operation, with the panel handling decompression, database recreation, and data import automatically.

Key Takeaways

A solid MySQL backup strategy has three pillars: automation, verification, and remote storage. Use --single-transaction for consistent InnoDB backups, always compress and encrypt your dumps, automate with cron, and implement a rotation strategy to balance recovery granularity with storage costs. Most importantly, regularly test your restore process. A backup that has never been restored is an assumption, not a guarantee. Make backup verification a scheduled task, not an emergency discovery.

Security-first hosting panel

Run your servers on a modern panel.

Panelica is a modern, security-first hosting panel — isolated services, built-in Docker and AI-assisted management, with one-click migration from any panel.

Zero-downtime migration Fully isolated services Cancel anytime
Share:
When did you last test a restore?