How to Backup MySQL with mysqldump and Restore a Database on Linux
Learn how to backup MySQL with mysqldump and restore a database on Linux with ready-to-copy commands, transactional consistency, and validation.
Losing a MySQL database to a wrong table drop, a corrupted data file, or a
bad migration happens to every team. The difference between a short
incident and a full day of panic is having a recent, validated, and
restorable backup. This tutorial covers the full workflow of backing up
with mysqldump and restoring later, including the flags that actually
matter in production.
The focus here is practical: copy-ready commands, parameters explained in context, and validation of the generated file before you need it for real. The persona is a developer who knows basic MySQL (SELECT/INSERT) but has never set up their own backup routine — probably migrating from shared hosting to their own VPS where the responsibility is now yours.
Estimated time: 15-20 minutes to run the full tutorial against a sample database. In real production, the dump itself takes time proportional to the database size (~1 GB/min on a typical NVMe SSD).
Prerequisites
Ubuntu 22.04 LTS or 24.04 LTS (Debian also works) with MySQL 8.0 or
MariaDB 10.6+ installed and running. sudo access on the server and
credentials for the MySQL root user or a user with SELECT,
LOCK TABLES, SHOW VIEW, and EVENT privileges on the database you
intend to dump. Free disk space at least equal to the database size
(check with df -h).
To confirm that mysqldump is available, run:
mysqldump --version
The output should show something like mysqldump Ver 8.0.x for Linux on x86_64.
If the command is not found, install the client package:
sudo apt update
sudo apt install -y mysql-client
For MariaDB, the equivalent binary is mariadb-dump (an alias for
mysqldump in recent distributions).
Backing up the database with mysqldump
mysqldump reads the current state of MySQL and generates an SQL file
with every command needed to rebuild the database from scratch — CREATE
DATABASE, CREATE TABLE, INSERT for each row, triggers, views, and
procedures. It is a portable, versionable format (you can even commit it
to Git, although that is not its primary use).
Take a simple dump of a specific database:
mysqldump -u root -p mydb > mydb-2026-05-29.sqlThe -u root sets the user, -p prompts for the password interactively
(safer than passing -ppassword on the command line, which leaks into
the shell history and into ps aux). The > redirection saves the
output to the named file.
Include the date in the file name from the start — it helps with rotation and prevents accidental overwrites.
Add the transactional consistency flags for production:
mysqldump -u root -p \
--single-transaction \
--routines \
--triggers \
--events \
--quick \
mydb > mydb-2026-05-29.sql--single-transaction wraps the dump in START TRANSACTION with
REPEATABLE READ isolation, producing a consistent snapshot without
blocking writes on InnoDB tables. That is the critical flag in
production: without it, parallel writes can leave the dump inconsistent.
The --routines, --triggers, and --events flags include stored
procedures, triggers, and scheduled events — things that are easy to
forget and that you only notice are missing when you restore and the
system breaks. --quick does the dump row-by-row instead of loading
whole tables into RAM, important for large tables.
Compress on the fly to save space:
mysqldump -u root -p \
--single-transaction --routines --triggers --events --quick \
mydb | gzip > mydb-2026-05-29.sql.gzThe pipe to gzip shrinks the file by 70-85% for typical dumps (SQL
text compresses very well). For 10 GB databases, that is the difference
between 2 GB and 10 GB taking space on your backup disk.
For better (slower) compression, swap gzip for zstd -19 or
xz -9.
Back up all databases at once (useful for a daily snapshot of the entire server):
mysqldump -u root -p \
--all-databases \
--single-transaction \
--routines --triggers --events \
--master-data=2 \
| gzip > full-2026-05-29.sql.gz--all-databases also includes the system databases (mysql,
sys, performance_schema) — preserving users and grants.
--master-data=2 records the binary log position in the dump as a
comment, useful for point-in-time recovery if you have binlogs enabled.
To avoid typing the password in automated scripts, create ~/.my.cnf
with [client] + user=root + password=secret and permission 600
(chmod 600 ~/.my.cnf). mysqldump reads it automatically — you run
mysqldump mydb > file.sql without -u or -p. Never put a password
in a versioned script.
Validating the backup file
A backup you never tested restoring is hearsay, not a backup. Before trusting the file, validate three things: file integrity, SQL syntax, and that the dump covers what you expect.
Check that the file was not truncated:
ls -lh mydb-2026-05-29.sql.gz
gzip -t mydb-2026-05-29.sql.gz && echo "OK: file is intact"gzip -t tests the compression integrity without decompressing. If it
fails, the file is corrupted (usually disk full during the dump or a
crash mid-way). Re-run the dump.
Inspect the dump content:
zcat mydb-2026-05-29.sql.gz | head -30
zcat mydb-2026-05-29.sql.gz | grep -c "^INSERT INTO"
zcat mydb-2026-05-29.sql.gz | grep "^CREATE TABLE" | wc -lhead shows the header with the mysqldump version and settings.
grep -c INSERT counts insert lines (a sanity check on data volume).
grep CREATE TABLE | wc -l counts tables — does it match what you
expect to have in the database?
To compare against the original server:
mysql -u root -p -e "SELECT COUNT(*) AS tables FROM information_schema.tables WHERE table_schema='mydb';"The numbers should match.
Restoring a database from the dump
Restore is the inverse path: read the SQL file and send it to a MySQL server that runs each command in sequence. This can be a new server, the same server after an accidental drop, or cloning production data to a staging environment.
Create the empty destination database (only if it does not already exist):
mysql -u root -p -e "CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Always use utf8mb4 — it supports emoji and full Asian characters.
You can skip this step if the dump was made with --databases (which
includes CREATE DATABASE in the file) or --all-databases.
Restore the compressed dump directly through a pipe:
zcat mydb-2026-05-29.sql.gz | mysql -u root -p mydbzcat decompresses in memory and sends it to the mysql client, which
executes each command against the mydb database. For an uncompressed
.sql file, use redirection:
mysql -u root -p mydb < mydb-2026-05-29.sqlThe operation takes time proportional to the size (each INSERT runs
individually by default). For large dumps, leave it running inside
tmux or screen so it does not stop if the SSH session drops.
Track progress on large dumps:
pv mydb-2026-05-29.sql.gz | zcat | mysql -u root -p mydbpv (pipe viewer) shows a progress bar and speed. Install it with
sudo apt install -y pv. Without it, the restore runs silently — you
have no idea whether you are at 10% or 90%.
If the database already has tables, the dump (which contains
DROP TABLE IF EXISTS) will delete the current versions and recreate
them. In production, restore into a database with a different name
first (mydb_restore) and validate before promoting via RENAME.
Post-restore verification
A restore that was not verified is a half restore. Check three things: the tables came back, the row counts match, and the application can open a connection.
Confirm structure and counts:
mysql -u root -p mydb -e "SHOW TABLES;"
mysql -u root -p mydb -e "SELECT COUNT(*) FROM users;"The first lists the tables; the second counts rows on a table for which you know the expected count. Discrepancies indicate an incomplete dump (a missing flag) or an interrupted restore.
--single-transaction --routines --triggers --quick --master-data=2 Troubleshooting
”Access denied” error during the dump
The user is missing privileges on some table. Check grants with
SHOW GRANTS FOR 'user'@'localhost';. For a complete dump, the user
needs SELECT, LOCK TABLES, SHOW VIEW, EVENT, and TRIGGER. The
simplest fix is to run as root.
Restore fails with “Unknown collation utf8mb4_0900_ai_ci”
The dump was generated on MySQL 8.0+ and you are restoring on 5.7 or
an older MariaDB. Edit the file (sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' file.sql)
or regenerate the dump with --compatible=mysql57.
”MySQL server has gone away” mid-restore
max_allowed_packet is too small on the destination server. Increase
it in /etc/mysql/mysql.conf.d/mysqld.cnf with max_allowed_packet = 256M,
restart MySQL, and try again.
Next steps
A manual backup is the start — automate it with cron and copy it to external storage (rclone to B2/S3 works well). Consider also master-slave replication for a warm copy available in seconds, and enable binary logs for point-in-time recovery within the day.
If you are running MySQL in production, a Hostini VPS already
comes with NVMe SSDs and disk snapshots as an additional protection
layer on top of the logical backup from mysqldump — fast recovery
for infrastructure disasters, without replacing the SQL dump that is
portable across servers.
Frequently asked questions
What is the difference between mysqldump and a disk snapshot?
mysqldump produces a portable SQL file (text with CREATE TABLE and INSERTs) that can be restored on any compatible MySQL server, even a different version. A disk snapshot is a binary copy — faster on large databases, but it only restores on the same engine/version and requires care with in-flight transactions. For databases under 50 GB, mysqldump is the standard.
Does mysqldump lock the tables during the backup?
By default yes, with FLUSH TABLES WITH READ LOCK on MyISAM. On InnoDB, use --single-transaction to perform the dump inside a consistent transaction without blocking writes. That is the most important flag for production databases.
Can I restore the dump on a different MySQL version?
Yes, as long as you go from the older to the newer version (5.7 → 8.0, for example). The reverse may fail due to new, unsupported syntax. For a cross-version downgrade, generate the dump with --compatible=mysql57 on the newer server.
How do I back up only a few tables?
Pass the table names after the database name: mysqldump -u root -p mydb users orders > partial.sql. That dumps only users and orders from the mydb database. Useful for migrating specific data without moving the entire database.
The .sql file is huge — how can I reduce it?
Compress it with gzip in the pipe: mysqldump ... | gzip > backup.sql.gz. Compression typically shrinks the file by 70-85%. To restore, use zcat backup.sql.gz | mysql -u root -p mydb. This avoids using temporary disk space during the operation.
How do I schedule the backup to run automatically every day?
Use cron: create /etc/cron.daily/mysql-backup with the dump and rotation script. Store the password in ~/.my.cnf with permission 600 to avoid passing it on the command line. Combine with rsync or rclone to copy to external storage right after the dump.