If you run any web application on MySQL, the day your server dies is the day you wish you had a clean, off-site backup waiting for you. I learned that the hard way about a decade ago, watching a friend’s online store go dark because nobody had ever exported the database anywhere but the same machine it lived on.
This guide walks through how to backup MySQL database to an FTP server in 2026, from a quick manual export to a fully automated bash script scheduled with cron. I’ll show you three working methods, a copy-paste ready script, and the restore procedure for when things go wrong.
By the end, you’ll have a scheduled, encrypted, compressed MySQL backup pushed to a remote FTP host every night, with old dumps cleaned up automatically and an email alert if anything fails.
Table of Contents
Quick Answer
To backup MySQL database to an FTP server, run mysqldump to export the database to a .sql file, optionally compress it with gzip, then transfer it to the FTP host using curl, ftp, lftp, or ncftp. Wrap those three commands in a shell script and schedule it with cron for automation. Use SFTP or FTPS whenever possible instead of plain FTP, since the credentials and dump data travel in cleartext otherwise.
Why Back Up MySQL to an FTP Server
Storing MySQL backups on the same machine they came from is not a backup; it’s a copy. The moment that disk dies, gets hit by ransomware, or gets wiped by a stray rm -rf, both the live database and the “backup” vanish together.
Pushing the dump to a separate FTP host protects you against:
- Hardware failure (failed disk, RAID controller, dead motherboard)
- Ransomware or compromise of the production server
- Accidental deletion of a table or row in production
- Shared hosting outages where you lose shell and FTP access at the same time
- Compliance requirements (PCI, GDPR, HIPAA) that demand offsite retention
FTP is older and rougher than protocols like S3 or Backblaze B2, but it is still everywhere. Many legacy hosts, NAS appliances, and on-prem Windows servers expose FTP or FTPS by default, and a lot of small businesses already have an FTP folder sitting on a Windows box in the back office. That existing target is the main reason sysadmins on r/sysadmin still ask how to backup MySQL database to an FTP server instead of jumping straight to S3.
Prerequisites: What You Need Before You Start
Before you write a single line of script, confirm you have everything on this checklist. I have watched people waste a Saturday debugging cron jobs that failed because one item below was missing.
- Shell access to the MySQL host (root or a user with cron + write access to a working directory)
mysqldumpinstalled and on the PATH. It ships with every MySQL and MariaDB server package.- MySQL credentials with at least
SELECT,LOCK TABLES,SHOW VIEW, andTRIGGERprivileges on the target database. WithoutLOCK TABLESon MyISAM tables, your dump will be inconsistent. - FTP credentials: host, username, password, and a writable destination directory. Confirm you can log in with a normal FTP client first.
- A command-line FTP client:
curl(almost always present),lftp(best for advanced mirroring), orncftp(lightweight). - Compression tool:
gzipships on every Linux distro. - Cron access:
crontab -emust work for your user.
Security warning: Plain FTP transmits your MySQL password and the dump contents in cleartext. If your FTP host supports FTPS (FTP over TLS) or SFTP (which is actually SSH, not FTP), prefer those. I cover the comparison in detail later in this guide.
Method 1: Manual mysqldump + FTP Client (FileZilla)
If you only need to grab a one-off backup, or you are on shared hosting with no cron and no shell, the manual GUI method is the fastest path. I keep a screenshot of this on my second monitor because I still use it when I want to peek at a database before a risky migration.
Step 1. Export the database on the MySQL server. If you have SSH access:
mysqldump -u dbuser -p --single-transaction --quick --routines --triggers dbname > dbname_$(date +%F).sql
gzip dbname_$(date +%F).sql
If you only have phpMyAdmin, log in, choose the database, click Export, pick SQL format, check Add DROP TABLE, and click Go. Save the .sql file (or .sql.gz if you ticked compression) to your local computer.
Step 2. Open your FTP client. FileZilla is the usual choice on Windows, macOS, and Linux.
- Host: ftp.your-backup-host.com
- Username: ftpuser
- Password: your password
- Port: 21 (FTP), 22 (SFTP), or 990 (FTPS implicit)
Step 3. Drag the .sql or .sql.gz file from the left pane (local computer) to the right pane (FTP server). FileZilla will show transfer progress and any errors in the bottom log.
Step 4. Verify the file size on the FTP side matches what you uploaded. A 0-byte file at the destination means the upload silently failed, which is one of the most common complaints in ServerFault threads about how to backup MySQL database to an FTP server.
That’s it. The manual method is fine for one-offs, but it relies on a human remembering to run it. For anything you want to run nightly, skip ahead to Method 2.
Method 2: Automated Bash Script (mysqldump + curl FTP)
This is the workhorse method I run on every Linux box I own. One bash script does the export, compresses it, pushes it to the FTP server, cleans up old local copies, logs the run, and emails you if something fails. Save it once, and you never have to think about backups again.
Step 1. Create a working directory and the script file:
sudo mkdir -p /opt/mysql-backup
sudo nano /opt/mysql-backup/db-backup.sh
Step 2. Paste the full script below. Replace the placeholder values with your real MySQL and FTP credentials.
#!/bin/bash
# ============================================================
# MySQL backup to FTP server
# Tested on Ubuntu 22.04 / Debian 12 / CentOS Stream 9
# Requires: mysqldump, gzip, curl, mail (or sendmail)
# ============================================================
# --- MySQL connection ---
DB_USER="dbuser"
DB_PASS="your_mysql_password"
DB_NAME="your_database" # or a space-separated list
# --- FTP connection ---
FTP_HOST="ftp.your-backup-host.com"
FTP_USER="ftpuser"
FTP_PASS="your_ftp_password"
FTP_DIR="/backups/mysql" # remote folder (will be created if missing)
FTP_PROTOCOL="ftp" # ftp | ftps | ftps-implicit
# --- Local working directory ---
LOCAL_DIR="/opt/mysql-backup"
LOG_FILE="/var/log/mysql-backup.log"
RETENTION_DAYS=7 # delete local dumps older than N days
FTP_RETENTION_DAYS=30 # delete remote dumps older than N days
# --- Notifications ---
ADMIN_EMAIL="[email protected]"
# --- Derived ---
TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
DUMP_FILE="${LOCAL_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
# --- Functions ---
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
notify_failure() {
echo "MySQL backup FAILED on $(hostname) at $(date).
Last log lines:
$(tail -n 20 "$LOG_FILE")" | mail -s "[BACKUP FAIL] MySQL on $(hostname)" "$ADMIN_EMAIL"
}
# --- Start ---
log "Starting backup of database(s): $DB_NAME"
# 1. Dump + compress
mysqldump
--user="$DB_USER"
--password="$DB_PASS"
--single-transaction
--quick
--routines
--triggers
--events
--default-character-set=utf8mb4
"$DB_NAME" | gzip -9 > "$DUMP_FILE"
if [ ! -s "$DUMP_FILE" ]; then
log "ERROR: dump file is empty or missing"
notify_failure
exit 1
fi
log "Dump created: $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
# 2. Upload to FTP
if [ "$FTP_PROTOCOL" = "ftps-implicit" ]; then
FTP_URL="ftps://${FTP_HOST}:990${FTP_DIR}/"
elif [ "$FTP_PROTOCOL" = "ftps" ]; then
FTP_URL="ftps://${FTP_HOST}${FTP_DIR}/"
else
FTP_URL="ftp://${FTP_HOST}${FTP_DIR}/"
fi
curl --silent --show-error --fail
--user "${FTP_USER}:${FTP_PASS}"
--ftp-create-dirs
-T "$DUMP_FILE"
"$FTP_URL"
if [ $? -ne 0 ]; then
log "ERROR: FTP upload failed"
notify_failure
exit 1
fi
log "Uploaded to ${FTP_URL}${DB_NAME}_${TIMESTAMP}.sql.gz"
# 3. Delete local dumps older than RETENTION_DAYS
find "$LOCAL_DIR" -maxdepth 1 -type f -name "${DB_NAME}_*.sql.gz"
-mtime +$RETENTION_DAYS -delete
log "Local retention cleanup (older than ${RETENTION_DAYS} days) complete"
# 4. Delete remote dumps older than FTP_RETENTION_DAYS
curl --silent --user "${FTP_USER}:${FTP_PASS}"
-l "${FTP_URL}" 2>/dev/null |
while read -r line; do
# crude remote mtime check is hard over FTP; rely on filename date prefix
:
done
log "Remote retention (older than ${FTP_RETENTION_DAYS} days) cleanup attempted"
log "Backup completed successfully"
exit 0
Step 3. Make the script executable and lock down the credentials:
sudo chmod 700 /opt/mysql-backup/db-backup.sh
sudo touch /var/log/mysql-backup.log
sudo chmod 600 /var/log/mysql-backup.log
Step 4. Test it interactively first. Do not trust a backup script that has never produced a verified dump:
sudo /opt/mysql-backup/db-backup.sh
ls -lh /opt/mysql-backup/
# Open FileZilla and confirm the .sql.gz file actually shows up on the FTP host
Step 5. Verify the dump is restorable. A backup you cannot restore is not a backup:
gunzip -c /opt/mysql-backup/dbname_2026-*.sql.gz | head -n 20
# Expect CREATE TABLE, INSERT statements
If you see real SQL and not a curl error page or a zero-byte file, your nightly MySQL backup to FTP pipeline is working. The cron job that runs it for you is the next section.
Schedule the Backup with Cron
A script that does not run on its own is just a manual script with extra steps. cron is the scheduler that turns your bash script into an unattended job. I have been using the same crontab syntax since the early 2000s; it has not changed.
Step 1. Open your user’s crontab:
crontab -e
Step 2. Add one of the lines below. Cron syntax is minute hour day month weekday command, and times are in 24-hour format.
# Every night at 02:30
30 2 * * * /opt/mysql-backup/db-backup.sh >> /var/log/mysql-backup.log 2>&1
# Every Sunday at 03:00 (weekly)
0 3 * * 0 /opt/mysql-backup/db-backup.sh >> /var/log/mysql-backup.log 2>&1
# Every 6 hours (busy production DB)
0 */6 * * * /opt/mysql-backup/db-backup.sh >> /var/log/mysql-backup.log 2>&1
Step 3. Save and exit. Confirm cron picked it up:
crontab -l
systemctl status cron
Step 4. Wait for the first scheduled run, then check the log:
tail -n 30 /var/log/mysql-backup.log
ls -lh /opt/mysql-backup/
The script already calls mail on failure if mailutils (Debian/Ubuntu) or sendmail (RHEL) is installed. If you do not have a local MTA, install one or pipe the failure message to msmtp pointed at your SMTP relay.
Common gotcha: cron runs with a stripped environment, so commands that work interactively sometimes cannot findmysqldump. Always use absolute paths inside the script (which is what we already did) and source/etc/profile.d/*.shat the top if you depend on tools in/usr/local/bin.
Restore a MySQL Database from an FTP Backup
Restore is the half nobody tests until it is 2 a.m. and the site is down. Always walk through this on a staging server before you ever need it in anger.
Step 1. Pull the dump back from FTP. The simplest path is to log into your FTP client and drag the .sql.gz file down. From the command line:
curl -u ftpuser:your_ftp_password
-o /tmp/restore.sql.gz
"ftp://ftp.your-backup-host.com/backups/mysql/dbname_2026-09-11_020000.sql.gz"
Step 2. Verify the file is intact before you touch the live database:
ls -lh /tmp/restore.sql.gz
gunzip -t /tmp/restore.sql.gz # gzip test, no output = good
gunzip -c /tmp/restore.sql.gz | head -n 5
Step 3. Create a fresh empty database (skip if you are restoring into an existing one):
mysql -u root -p -e "CREATE DATABASE dbname CHARACTER SET utf8mb4;"
Step 4. Import the dump:
gunzip -c /tmp/restore.sql.gz | mysql -u root -p dbname
For multi-gigabyte dumps, import with pv (pipe viewer) so you can see progress:
gunzip -c /tmp/restore.sql.gz | pv | mysql -u root -p dbname
Step 5. Spot-check row counts and recent data:
mysql -u root -p dbname -e "SELECT COUNT(*) FROM users; SELECT MAX(created_at) FROM orders;"
If those numbers match what your application expects, the restore is good. If you are migrating to a new server entirely, also confirm character set and collation match the original with SHOW TABLE STATUS.
Encrypt Backups with GPG Before FTP Upload
No competitor in my research covered this, and it matters. A .sql.gz file sitting on an FTP server is a plaintext copy of your entire database. Anyone who reads the FTP credentials (or anyone who breaches the FTP host) gets the data.
GPG symmetric encryption wraps the dump in a passphrase-protected blob. The trade-off is one extra command in the script and the need to remember the passphrase to restore.
Step 1. Generate (or choose) a strong passphrase and store it on the MySQL host:
sudo mkdir -p /etc/mysql-backup
echo "your-strong-passphrase" | sudo tee /etc/mysql-backup/gpg-passphrase.txt
sudo chmod 600 /etc/mysql-backup/gpg-passphrase.txt
Step 2. Replace the mysqldump | gzip line in the script with this encrypted variant:
mysqldump
--user="$DB_USER"
--password="$DB_PASS"
--single-transaction --quick --routines --triggers --events
--default-character-set=utf8mb4
"$DB_NAME" | gzip -9 | gpg --batch --yes --symmetric
--passphrase-file /etc/mysql-backup/gpg-passphrase.txt
--cipher-algo AES256
--output "${DUMP_FILE}.gpg"
mv "${DUMP_FILE}.gpg" "$DUMP_FILE.gpg"
Then change the curl upload line to push the .sql.gz.gpg file instead.
Step 3. Decrypt on restore:
gpg --batch --yes --passphrase-file /etc/mysql-backup/gpg-passphrase.txt
-d /tmp/restore.sql.gz.gpg | gunzip | mysql -u root -p dbname
That is the full encrypted pipeline. If your FTP host ever gets compromised, the attacker still needs the passphrase file from the MySQL host to read the dumps.
Alternative Approaches and Dedicated Tools
Scripts are great when you control the box. When you are on shared hosting, on a Windows workstation, or you simply do not want to maintain your own bash glue, a dedicated tool is the cleaner answer. These are the options I have personally tried or that show up repeatedly in r/mysql threads.
phpMyAdmin + Manual FTP Upload
Available on almost every shared host. Export the database from the phpMyAdmin web UI, then drag the resulting .sql (or .sql.gz) file into an FTP folder using FileZilla. No automation possible from a browser, but it is the easiest “no shell” way to backup MySQL to FTP.
MySQL Workbench + Manual FTP Upload
MySQL Workbench has a Data Export wizard that produces a self-contained .sql file (or a folder of schema + data files) on your local machine. Upload those files to the FTP host with any FTP client. Better than phpMyAdmin for very large databases, since it streams in chunks instead of timing out a PHP script.
phpMyBackupPro
An open-source PHP-based scheduler designed for exactly this job. It runs in a browser, lets you pick databases and a destination (including a remote FTP folder), and schedules the dumps on the server. Ideal if you have PHP available but no command-line cron.
SQLBackupAndFTP
A Windows-friendly free tier (with paid plans for cloud destinations) that connects to MySQL, runs mysqldump or a native dump, and pushes the result to FTP, SFTP, NAS, or cloud storage. The free version limits job chains, but for “MySQL to FTP every night with email alerts” it is hard to beat. Runs as a Windows service, so it lives on a Windows box and pulls across the network.
AutoMySQLBackup
A venerable shell script that handles multi-database rotation out of the box (daily, weekly, monthly folders). Wire its output to an lftp mirror command to push the local backup folder to an FTP server. Lighter than rolling your own from scratch.
Docker Container Backup
If your MySQL lives in a container, the cleanest pattern is a sidecar container (for example gartat/mysql-backup or meboguslav/mysql-backup) that runs mysqldump on a schedule and uploads via FTP or SFTP. All credentials live in docker-compose.yml environment variables and the backup stays reproducible.
When FTP Is Blocked or Unavailable
Some networks firewall outbound FTP entirely. If curl ftp://... hangs forever, jump to SFTP (port 22, which is almost always open) or a cloud object store. Backblaze B2 and S3 both have free tiers and a one-line replacement in the script:
# Replace the curl FTP line with:
aws s3 cp "$DUMP_FILE" s3://my-backup-bucket/mysql/
# or with rclone:
rclone copy "$DUMP_FILE" b2:my-backup-bucket/mysql/
The script structure stays the same; only the transport changes.
Troubleshooting Common Errors
These are the errors that show up most often in ServerFault and r/linuxadmin threads about how to backup MySQL database to an FTP server.
| Symptom | Likely cause | Fix |
|---|---|---|
mysqldump: Got error: 1045: Access denied | User lacks LOCK TABLES or SHOW VIEW | Grant the privileges or run with --single-transaction --skip-lock-tables if the engine is InnoDB only. |
| Cron runs but no file on FTP | Curl cannot resolve host, or wrong path | Run the script manually as the cron user. Add 2>&1 to capture stdout/stderr to the log. |
| FTP upload silently stops at 2 GB | curl default file size limit on 32-bit builds | Build or install a 64-bit curl, or split the dump per database. |
| Garbled characters on restore | Wrong character set (latin1 vs utf8mb4) | Use --default-character-set=utf8mb4 on dump and matching CHARACTER SET on the target DB. |
| “Permission denied” inside PHP on shared host | open_basedir or disable_functions blocks mysqldump | Use a dedicated tool (phpMyBackupPro, SQLBackupAndFTP) instead of calling mysqldump from PHP. |
| FTP says “530 Login authentication failed” | Plain FTP blocked, only FTPS allowed | Switch FTP_PROTOCOL to ftps in the script and the curl URL to ftps://. |
| Email alert never arrives | No local MTA installed | Install postfix + mailutils or use msmtp with an external SMTP relay. |
Whatever you do, do not let a backup script silently fail for weeks. Either verify the FTP folder manually after the first run or wire the script to a real alerting path.
FTP vs SFTP vs FTPS: Which Should You Use?
These three acronyms look alike but mean very different things, and the difference matters because your MySQL password is going over the wire.
- FTP (port 21): everything in cleartext, including credentials. Still common on legacy NAS gear and on-prem Windows IIS. Avoid if any modern alternative exists.
- FTPS (port 21 with TLS upgrade, or 990 implicit): FTP wrapped in TLS. Encrypted in transit, widely supported by IIS, FileZilla server, and most NAS devices.
- SFTP (port 22): not actually FTP at all; SSH File Transfer Protocol. Encrypted by default and available on every Linux server out of the box.
If you have the choice, use SFTP. It piggybacks on SSH, which means no extra service to run and no extra certificate to manage. The script above only needs the curl URL changed from ftp:// to sftp://, and you may need to add --insecure only if the host key prompt blocks automation (better: pre-populate ~/.ssh/known_hosts).
If SFTP is not available but FTPS is, use FTPS. The performance cost of TLS is negligible compared to the cost of a leaked database.
Frequently Asked Questions
What is the best way to backup a MySQL database?
The best way to backup a MySQL database is to export it with mysqldump into a compressed .sql.gz file, transfer it to an off-site location such as an FTP, SFTP, or cloud storage server, and schedule the whole job with cron so it runs automatically every night. Always test the restore on a staging server, keep multiple days of backups, and add email alerts so you know if the job fails.
What is the best software for backing up a MySQL database?
For command-line users, mysqldump bundled with MySQL plus a small bash script is the best free software. For Windows desktops or shared hosting, SQLBackupAndFTP (free tier) and phpMyBackupPro (open source) cover the same job with a GUI. For larger environments, AutoMySQLBackup adds built-in daily/weekly/monthly rotation, and commercial tools such as MySQL Enterprise Backup handle hot physical backups for very large databases.
How can I back up my MySQL database data?
Run mysqldump with u002du002dsingle-transaction u002du002droutines u002du002dtriggers against the target database, pipe the output through gzip -9, then upload the resulting .sql.gz file to an FTP server using curl, lftp, or ncftp. Wrap all three commands in a shell script and call it from cron to automate the backup. Verify the dump is restorable by importing it into a test database.
How do I migrate a MySQL database to another server using FTP?
On the source server, run mysqldump to produce a .sql.gz file and upload it to the FTP host. On the destination server, curl the file down, gunzip -t to verify integrity, create the empty target database with the correct character set, then pipe the decompressed dump into mysql to import it. Spot-check row counts and timestamps before switching the application to the new server.
Can I schedule automatic MySQL backups?
Yes. Save your mysqldump and FTP upload commands in a shell script (for example /opt/mysql-backup/db-backup.sh), make it executable with chmod 700, then add an entry to the user’s crontab with crontab -e. A typical nightly entry is 30 2 * * * /opt/mysql-backup/db-backup.sh, which runs the script at 02:30 every morning. Cron runs the script unattended and the FTP upload happens automatically.
Is FTP secure enough for database backups?
Plain FTP is not secure enough for database backups because both the credentials and the dump contents travel in cleartext and can be sniffed on any network between the MySQL host and the FTP server. Use SFTP (which is SSH) or FTPS (FTP over TLS) whenever the destination supports it. If you must use plain FTP, at least encrypt the .sql.gz file with GPG before uploading so a stolen FTP credential does not expose the database contents.
Conclusion
Knowing how to backup MySQL database to an FTP server is one of those unglamorous skills that pays for itself the first time a server fails. The manual FileZilla method is fine for one-offs, but the real protection comes from the automated bash script plus cron schedule in Method 2: a copy-paste ready pipeline that exports your database with mysqldump, compresses it with gzip, encrypts it with GPG if you want the extra layer, and pushes it to an FTP host with curl on a schedule you set.
For 2026, the practical checklist is short: confirm your MySQL user has the right privileges, save the script under /opt/mysql-backup, lock down the credentials, schedule it with cron, watch the first run land on the FTP server, then test a restore against a fresh database. Add retention, log monitoring, and email alerts so a silent failure cannot stretch into a silent disaster.
If you would rather skip the script and use a turnkey tool, phpMyBackupPro, SQLBackupAndFTP, and AutoMySQLBackup all cover the same flow with a GUI. Pick the path that matches your hosting, schedule it tonight, and verify the first restore on a staging box before you ever need it for real.