# MySQL and MariaDB (/docs/databases/mysql)

backupd backs up MySQL 5.7 and later, and MariaDB 10.3 and later, with `mysqldump` in a single transaction. Your app keeps writing while the backup runs: InnoDB tables are read from one consistent snapshot and no tables are locked. backupd detects MariaDB from the server version.

## Connection string [#connection-string]

```txt
mysql://user:password@db.example.com:3306/app?ssl-mode=REQUIRED
```

- The scheme is `mysql://`.
- The port defaults to `3306`.
- **The database name is required**, and each connection string backs up one database. Add each database you want backed up separately.
- Percent-encode special characters in the user or password.

## TLS [#tls]

backupd always connects with TLS. Set the mode with `ssl-mode` (or `sslmode`).

| `ssl-mode` | Result |
| --- | --- |
| not set | `REQUIRED` is used. |
| `REQUIRED`, `VERIFY_CA`, `VERIFY_IDENTITY` | Used as given. `VERIFY_CA` and `VERIFY_IDENTITY` check the server certificate. |
| `PREFERRED` | Upgraded to `REQUIRED`. |
| `DISABLED` | Rejected: _backupd connects over TLS; remove ssl-mode=DISABLED_ |

## Permissions [#permissions]

A read-only user is enough to back up. Because backups include routines, triggers and events, the user needs to be able to read those too. A typical grant:

```sql
CREATE USER 'backupd'@'%' IDENTIFIED BY 'choose-a-strong-password';
GRANT SELECT, SHOW VIEW, TRIGGER, EVENT ON app.* TO 'backupd'@'%';
```

On MySQL 8.0.20 and later, also grant `SHOW_ROUTINE` (a global privilege) so stored procedures and functions are included.

To restore from the app, the target needs a user that can create tables and insert data. In-app restores remove `DEFINER` clauses, so the target doesn't need `SUPER` or the original accounts.

## What is backed up [#what-is-backed-up]

- One database: its tables, views, data, routines, triggers and events.
- Tablespaces are not dumped.
- GTID state is not written to the dump, so it restores into any server.

Files are named with the `.sql.zst.age` extension: plain SQL from `mysqldump`, compressed with zstd and encrypted with age.

## Row counts and restore tests [#row-counts-and-restore-tests]

backupd counts every table's rows exactly from the dump as it is written. The restore test loads the backup into a temporary database on a sandbox MySQL server and compares each table. See [Integrity checks and restore tests](/docs/backups/verification).

## Restoring [#restoring]

Before an in-app restore starts, backupd checks whether the target already has any of the backup's tables. If it does, the restore stops with a message naming them, unless you tick **Replace tables that already exist in the target**.

To restore yourself, see [Restore without backupd](/docs/restoring/without-backupd).

## Limitations [#limitations]

- One database per connection string.
- No point-in-time recovery or incremental backups. Every backup is a full copy.
- No private networks or SSH tunnels.
- A dump that sends no data for 5 minutes is stopped.
