Automate Nightly MySQL/MariaDB Backup with mysqldump
Databases are only as safe as their last verified backup. Running mysqldump on a nightly cron creates compressed, timestamped dump files automatically, but cron failures are silent by default — you only discover the backup stopped working when you need to restore. Pairing the script with a CronJobPro heartbeat monitor closes that gap: if the ping does not arrive within the expected window, you get an alert before disaster strikes.
Schedule
0 2 * * *Every night at 2:00 AM server time
Setup
- 1
Create a dedicated backup directory
Choose a location outside the web root, for example /var/backups/mysql, and make sure it is owned by the user that will run the script. Run: mkdir -p /var/backups/mysql && chmod 700 /var/backups/mysql
- 2
Store credentials securely in a MySQL options file
Create /root/.my.cnf (or ~/.my.cnf for the cron user) with contents: [mysqldump] user=backup_user password=yourpassword Then restrict permissions: chmod 600 ~/.my.cnf. This keeps the password out of the script and out of process listings.
- 3
Create the backup script
Save the script below to /usr/local/bin/mysql-nightly-backup.sh and make it executable: chmod +x /usr/local/bin/mysql-nightly-backup.sh. Edit the variables at the top to match your database name, backup directory, retention policy, and CronJobPro heartbeat token.
- 4
Add the cron entry
Run crontab -e (as root or the designated backup user) and add the line that matches the schedule: 0 2 * * * /usr/local/bin/mysql-nightly-backup.sh >> /var/log/mysql-backup.log 2>&1
- 5
Create a CronJobPro heartbeat monitor and test
In CronJobPro, create a new Heartbeat monitor, set the period to 24 hours, and set a grace period of 30 minutes. Copy the generated ping URL into your script (replacing the placeholder token). Do a manual test run with bash /usr/local/bin/mysql-nightly-backup.sh and confirm the heartbeat registers as received in the CronJobPro dashboard.
The script
bash
#!/usr/bin/env bash
set -euo pipefail
# --- Configuration ---
DB_NAME="your_database" # or "--all-databases" for all DBs
BACKUP_DIR="/var/backups/mysql"
RETAIN_DAYS=7 # delete dumps older than this
HEARTBEAT_TOKEN="your_token_here" # from CronJobPro heartbeat monitor
# --- Derived values ---
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
PING_URL="https://cronjobpro.com/ping/${HEARTBEAT_TOKEN}"
# Signal failure to CronJobPro if the script exits with an error
trap 'curl -fsS --retry 3 "${PING_URL}/fail" > /dev/null 2>&1 || true' ERR
echo "[$(date)] Starting backup of '${DB_NAME}'"
# --- Run backup ---
# Credentials are read from ~/.my.cnf — no password in the command line
mysqldump \
--single-transaction \
--routines \
--triggers \
--events \
"${DB_NAME}" \
| gzip -9 > "${FILE}"
echo "[$(date)] Backup written to ${FILE} ($(du -sh "${FILE}" | cut -f1))"
# --- Prune old backups ---
find "${BACKUP_DIR}" -name "${DB_NAME}_*.sql.gz" -mtime +"${RETAIN_DAYS}" -delete
echo "[$(date)] Old backups pruned (retention: ${RETAIN_DAYS} days)"
# --- Heartbeat ping on success ---
curl -fsS --retry 3 "${PING_URL}" > /dev/null 2>&1
echo "[$(date)] Heartbeat ping sent"
Monitor it
Create a Heartbeat monitor in CronJobPro for this backup job. Set the period to 24 hours and the grace period to at least 30 minutes to allow for occasional slow disks or large databases. CronJobPro will generate a unique URL in the form https://cronjobpro.com/ping/your_token. The script above calls that URL at the end of a successful run, and calls /ping/your_token/fail via the ERR trap if anything goes wrong mid-script. If CronJobPro does not receive the ping within the 24-hour period plus the grace window, it sends an alert to whichever channels you have configured — email, Slack, Discord, Teams, PagerDuty, Opsgenie, or a custom webhook. This means you are notified both when the backup script errors out (via the /fail signal) and when it silently stops running altogether, such as when cron is misconfigured, the server is down, or the cron user's permissions are revoked.
Frequently asked questions
Why use --single-transaction instead of locking tables?
For InnoDB tables, --single-transaction starts a consistent read snapshot without acquiring table locks, so the database remains fully accessible during the dump. For MyISAM tables it does not help, and you may need --lock-tables (the default) instead. Check your storage engine with SHOW TABLE STATUS.
How do I back up all databases at once?
Replace the DB_NAME variable value with the flag --all-databases and update the mysqldump line to use that flag directly instead of "${DB_NAME}". Also update the FILE naming pattern so it does not include the database name literally, for example use "all-databases_${TIMESTAMP}.sql.gz".
What happens if the server is off when cron fires at 2 AM?
Standard cron does not retry missed jobs. If uptime reliability is a concern, use a scheduling tool that supports missed-run catch-up, or move the job to a CronJobPro HTTP-triggered schedule so CronJobPro tracks whether the run was acknowledged. With a heartbeat monitor you will at minimum receive an alert that the expected ping never arrived.
How much disk space will these backups use?
That depends entirely on your database size and data compressibility. The gzip -9 flag maximises compression. A mostly-text database often compresses to 10-20% of its raw size. Monitor /var/backups/mysql with df -h and adjust RETAIN_DAYS to balance safety with available space.