How to back up and restore MySQL and MariaDB with mysqldump
For InnoDB tables, run mysqldump --single-transaction --routines --events mydb > mydb.sql to get a consistent dump without locking the database, and restore it with mysql mydb < mydb.sql. Keep the password in an option file, never on the command line. On MariaDB the tool is called mariadb-dump and takes the same options.
mysqldump or mariadb-dump?
MariaDB renamed its clients to mariadb-dump and mariadb. mysqldump still works as a symlink, but MariaDB deprecated that name in 11.0. Unless marked otherwise, every option in this guide works with both tools. Where you see mysql, MariaDB users can type mariadb.
Create a backup user and an option file
A password typed after -p shows up in ps output for other users and in your shell history. The MYSQL_PWD environment variable is worse: MySQL calls it extremely insecure and deprecated it in 8.4. Use a dedicated account and an option file instead.
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'your-password-here';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, PROCESS, EVENT ON *.* TO 'backup'@'localhost';Those are the privileges the MySQL docs list for mysqldump, plus EVENT for dumping events. On MySQL with GTIDs enabled, --single-transaction also needs RELOAD or FLUSH_TABLES.
[client]
user=backup
password="your-password-here"sudo chmod 600 /etc/mysql/backup.cnfQuote the password if it contains #, which otherwise starts a comment. Pass the file with --defaults-extra-file, and put that option first on the command line or it does not work.
Back up one database
mysqldump --defaults-extra-file=/etc/mysql/backup.cnf --single-transaction --routines --events --triggers mydb > mydb.sql--single-transactionstarts a transaction and dumps from one consistent read. InnoDB tables stay fully usable during the dump. While it runs, avoidALTER TABLE,CREATE TABLE,DROP TABLE,RENAME TABLEandTRUNCATE TABLE; they can make the dump invalid.--routinesadds stored procedures and functions. It is off by default.--eventsadds scheduled events. It is off by default.--triggersadds triggers. It is on by default; listing it makes the intent clear.
A dump of one named database has no CREATE DATABASE or USE statements. That is useful: you choose the target database at restore time, and it can have a different name.
Several databases or the whole server
--databases treats every name as a database and writes CREATE DATABASE and USE statements for each. --all-databases dumps every database, including the mysql system schema with accounts and grants.
mysqldump --defaults-extra-file=/etc/mysql/backup.cnf --single-transaction --routines --events --databases shop blog > two-databases.sqlmysqldump --defaults-extra-file=/etc/mysql/backup.cnf --single-transaction --routines --events --all-databases > all-databases.sqlOn MariaDB, mariadb-dump --system=users writes accounts as portable CREATE USER statements instead of raw rows from the system tables, which makes them easier to move between versions.
Compress the dump
SQL text compresses well. Pipe it through gzip:
mysqldump --defaults-extra-file=/etc/mysql/backup.cnf --single-transaction --routines --events mydb | gzip > mydb.sql.gzIf mysqldump fails partway, the pipe still exits with gzip's status, 0. In scripts, use set -o pipefail. Also note that mysqldump's own --compress option only compresses traffic between client and server, not the file, and MySQL has deprecated it.
MyISAM and other non-transactional tables
--single-transaction only gives a consistent view of transactional tables such as InnoDB. MyISAM tables can still change mid-dump. Find them first:
SELECT table_schema, table_name, engine FROM information_schema.tables
WHERE engine <> 'InnoDB'
AND table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys');If any matter to you, convert them to InnoDB or replace --single-transaction with --lock-all-tables (-x). That takes a global read lock for the whole dump: consistent across every engine, but writes wait until it finishes.
Restore
Restore as an account that can create databases and objects, such as root; the backup user is read-only. -p with no value prompts for the password. A single-database dump needs an existing database, so create one first:
mysql -u root -p -e "CREATE DATABASE mydb_restored"gunzip < mydb.sql.gz | mysql -u root -p mydb_restoredDumps made with --databases or --all-databases already contain CREATE DATABASE and USE, so load them with mysql -u root -p < all-databases.sql and no database name.
Restoring over an existing database replaces each table in the dump, because mysqldump writes a DROP TABLE before every CREATE TABLE by default. Tables that are not in the dump are left alone.
Import stopping with an error such as ERROR 1227 or Unknown collation? How to import a SQL file into MySQL or MariaDB explains and fixes the errors people hit most.
Large databases
--quickis on by default. It streams rows one at a time instead of loading each table into memory. Do not turn it off.- Restores are slow. The server replays every INSERT and rebuilds every index. The MySQL docs say plainly that mysqldump is not a fast or scalable way to back up large amounts of data; for those, look at physical backups or disk snapshots.
- Dump from a replica if you have one, so the extra reads land there.
- Skip huge tables you can rebuild with
--ignore-table=mydb.logs. Repeat the option for each table. - If a restore fails on a large row, raise
max_allowed_packeton both the client (mysql --max_allowed_packet=1G) and the server. 1G is the maximum.
On a busy primary, take the dump from a replica instead, so the backup adds no load where your users are: see how to back up MySQL from a replica.
Verify the dump
A complete dump ends with a -- Dump completed on comment. A dump that stopped early does not:
gunzip -c mydb.sql.gz | tail -n 1That proves the file is whole, not that it restores. Restore it into a scratch database as shown above, then compare row counts for your key tables against production. -N drops the column header:
mysql --defaults-extra-file=/etc/mysql/backup.cnf -N -e "SELECT COUNT(*) FROM mydb_restored.orders"Run it every night with cron
The script writes to a temporary name, checks for the completion line, renames the file, and deletes dumps older than seven days.
#!/usr/bin/env bash
set -euo pipefail
DB="mydb"
BACKUP_DIR="/var/backups/mysql"
KEEP_DAYS=7
OUT="$BACKUP_DIR/$DB-$(date +%Y-%m-%d_%H%M).sql.gz"
mkdir -p "$BACKUP_DIR"
trap 'rm -f "$OUT.partial"' EXIT
mysqldump --defaults-extra-file=/etc/mysql/backup.cnf \
--single-transaction --routines --events --triggers "$DB" \
| gzip > "$OUT.partial"
gunzip -c "$OUT.partial" | tail -n 1 | grep -q "Dump completed"
mv "$OUT.partial" "$OUT"
find "$BACKUP_DIR" -name "$DB-*.sql.gz" -type f -mtime +"$KEEP_DAYS" -delete15 2 * * * root /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1Make the script executable with chmod 755. Then copy the dumps off the server: a backup on the same disk goes down with it.
To stream each dump straight to S3-compatible storage instead of keeping it on the server, see how to back up a database to S3 automatically.
Common errors
| Error | Fix |
|---|---|
Access denied for user 'backup'@'localhost' (using password: YES) | Wrong password, or the option file was not read. Put --defaults-extra-file first and quote passwords that contain #. |
Access denied; you need (at least one of) the PROCESS privilege(s) for this operation | Grant PROCESS, or add --no-tablespaces if you do not use tablespaces. |
Unknown database 'mydb_restored' | A single-database dump has no CREATE DATABASE. Create the database first. |
Got a packet bigger than 'max_allowed_packet' bytes | Raise max_allowed_packet on the client and the server. |
Access denied; you need (at least one of) the ... privilege(s) while restoring views, triggers or routines | The dump names another account as DEFINER. Restore as an admin account, or create that account first. |
A dump restores to the moment it was taken. To recover up to just before a mistake, keep the binary logs as well: see point-in-time recovery for MySQL. For RDS or DigitalOcean databases, see managed database backups.
Frequently asked questions
- Does mysqldump lock tables?
- By default it locks the tables of each database while dumping them. With --single-transaction, InnoDB tables are read from a consistent snapshot and stay writable.
- Is mariadb-dump the same as mysqldump?
- It is MariaDB's version of the same tool with the same core options. mysqldump remains as a deprecated alias on MariaDB.
- How do I back up every MySQL database at once?
- Use mysqldump --all-databases with --single-transaction --routines --events. Restore it with mysql < file.sql, without naming a database.
- How do I dump only the table structure?
- Add --no-data. mysqldump writes the CREATE statements without any rows.
- How do I dump a single table?
- Name it after the database: mysqldump mydb orders > orders.sql. List several tables to dump several.
How this was checked
Commands, limits and prices were checked against these official pages, on October 3, 2026:
- MySQL 8.4 Reference Manual: mysqldump
- MySQL 8.4 Reference Manual: Dumping Data in SQL Format with mysqldump
- MySQL 8.4 Reference Manual: Reloading SQL-Format Backups
- MySQL 8.4 Reference Manual: Dumping Stored Programs
- MySQL 8.4 Reference Manual: Establishing a Backup Policy
- MySQL 8.4 Reference Manual: End-User Guidelines for Password Security
- MySQL 8.4 Reference Manual: Command-Line Options that Affect Option-File Handling
- MySQL 8.4 Reference Manual: Packet Too Large
- MySQL 8.4 Server Error Message Reference
- MariaDB Knowledge Base: mariadb-dump