Configure MySQL binary log backup and point-in-time recovery

Advanced 45 min May 14, 2026 529 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up automated MySQL binary log backup with rotation and implement point-in-time recovery procedures for complete database disaster recovery and operational continuity.

Prerequisites

  • MySQL 8.0+ installed and running
  • Root or sudo access
  • Minimum 10GB free disk space for binary log storage
  • Basic MySQL administration knowledge

What this solves

MySQL binary logs record every data modification operation, enabling point-in-time recovery to any second between full backups. This tutorial configures automated binary log backup with rotation, implements recovery procedures, and sets up monitoring to ensure your MySQL database can be restored to any specific moment in time.

Step-by-step configuration

Enable MySQL binary logging

Binary logging must be enabled in MySQL configuration to record all data-modifying operations for point-in-time recovery.

# Binary logging configuration
log-bin = /var/log/mysql/mysql-bin
server-id = 1
binlog_format = ROW
binlog_row_image = FULL
expire_logs_days = 7
max_binlog_size = 1G
sync_binlog = 1
binlog_cache_size = 1M

Create binary log directory

Set up the directory structure for binary logs with proper permissions and ownership for MySQL access.

sudo mkdir -p /var/log/mysql
sudo chown mysql:mysql /var/log/mysql
sudo chmod 750 /var/log/mysql

Restart MySQL service

Apply the binary logging configuration by restarting the MySQL service.

sudo systemctl restart mysql
sudo systemctl status mysql

Verify binary logging is active

Confirm binary logging is working and check the current log file status.

sudo mysql -e "SHOW VARIABLES LIKE 'log_bin';"
sudo mysql -e "SHOW MASTER STATUS;"
sudo mysql -e "SHOW BINARY LOGS;"

Create binary log backup directory

Set up dedicated storage for binary log backups with appropriate structure and permissions.

sudo mkdir -p /backup/mysql/binlogs
sudo mkdir -p /backup/mysql/binlogs/archive
sudo chown mysql:mysql /backup/mysql/binlogs
sudo chmod 750 /backup/mysql/binlogs

Create binary log backup script

Implement automated backup script that safely copies and compresses binary logs while maintaining recovery capability.

#!/bin/bash

# MySQL Binary Log Backup Script
# Safely backs up and rotates binary logs

set -euo pipefail

# Configuration
MYSQL_USER="backup_user"
MYSQL_PASSWORD="secure_backup_password"
BINLOG_DIR="/var/log/mysql"
BACKUP_DIR="/backup/mysql/binlogs"
ARCHIVE_DIR="/backup/mysql/binlogs/archive"
RETENTION_DAYS=30
LOG_FILE="/var/log/mysql-binlog-backup.log"

# Logging function
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

log "Starting binary log backup"

# Get list of binary logs except the current one
BINLOGS=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW BINARY LOGS;" | tail -n +2 | head -n -1 | awk '{print $1}')

if [ -z "$BINLOGS" ]; then
    log "No binary logs to backup"
    exit 0
fi

# Create dated backup directory
BACKUP_DATE=$(date '+%Y%m%d')
DATE_DIR="$BACKUP_DIR/$BACKUP_DATE"
mkdir -p "$DATE_DIR"

# Backup and compress each binary log
for binlog in $BINLOGS; do
    if [ -f "$BINLOG_DIR/$binlog" ]; then
        log "Backing up $binlog"
        cp "$BINLOG_DIR/$binlog" "$DATE_DIR/"
        gzip "$DATE_DIR/$binlog"
        log "Compressed $binlog to $DATE_DIR/$binlog.gz"
    fi
done

# Flush logs to create new binary log
mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "FLUSH BINARY LOGS;"
log "Flushed binary logs"

# Purge old binary logs (keep last 3 days worth)
mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);"
log "Purged old binary logs"

# Archive old backups
find "$BACKUP_DIR" -type d -name "[0-9]*" -mtime +7 -exec mv {} "$ARCHIVE_DIR/" \;

# Clean up very old archives
find "$ARCHIVE_DIR" -type d -name "[0-9]*" -mtime +$RETENTION_DAYS -exec rm -rf {} \;

log "Binary log backup completed successfully"

# Generate backup report
BACKUP_SIZE=$(du -sh "$DATE_DIR" | cut -f1)
BINLOG_COUNT=$(ls -1 "$DATE_DIR" | wc -l)
log "Backup summary: $BINLOG_COUNT files, $BACKUP_SIZE total size"

Make backup script executable

Set proper permissions on the backup script for execution by the system.

sudo chmod +x /usr/local/bin/mysql-binlog-backup.sh
sudo chown mysql:mysql /usr/local/bin/mysql-binlog-backup.sh

Create MySQL backup user

Create a dedicated user account with minimal privileges required for binary log operations.

sudo mysql -e "CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'secure_backup_password';"
sudo mysql -e "GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'backup_user'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"

Set up automated backup schedule

Configure systemd timer for regular binary log backup execution.

[Unit]
Description=MySQL Binary Log Backup
After=mysql.service
Requires=mysql.service

[Service]
Type=oneshot
User=mysql
Group=mysql
ExecStart=/usr/local/bin/mysql-binlog-backup.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=MySQL Binary Log Backup Timer
Requires=mysql-binlog-backup.service

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Enable backup automation

Start and enable the systemd timer for automated binary log backups.

sudo systemctl daemon-reload
sudo systemctl enable mysql-binlog-backup.timer
sudo systemctl start mysql-binlog-backup.timer
sudo systemctl status mysql-binlog-backup.timer

Create point-in-time recovery script

Implement recovery script that can restore database to any specific point in time using full backup and binary logs.

#!/bin/bash

# MySQL Point-in-Time Recovery Script
# Restores database to specific point in time

set -euo pipefail

# Check arguments
if [ $# -ne 2 ]; then
    echo "Usage: $0 

Make recovery script executable

Set execution permissions on the point-in-time recovery script.

sudo chmod +x /usr/local/bin/mysql-pitr-recovery.sh
sudo chown root:mysql /usr/local/bin/mysql-pitr-recovery.sh
sudo chmod 750 /usr/local/bin/mysql-pitr-recovery.sh

Create monitoring script

Set up monitoring for binary log health and backup status to ensure recovery capability.

#!/bin/bash

# MySQL Binary Log Monitoring Script
# Monitors binlog health and backup status

set -euo pipefail

MYSQL_USER="backup_user"
MYSQL_PASSWORD="secure_backup_password"
BINLOG_DIR="/var/log/mysql"
BACKUP_DIR="/backup/mysql/binlogs"
ALERT_LOG="/var/log/mysql-binlog-alerts.log"
MAX_BINLOG_AGE_HOURS=24
MAX_BACKUP_AGE_HOURS=48

log_alert() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ALERT: $1" | tee -a "$ALERT_LOG"
}

log_info() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - INFO: $1"
}

# Check if binary logging is enabled
BINLOG_STATUS=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW VARIABLES LIKE 'log_bin';" | tail -1 | awk '{print $2}')

if [ "$BINLOG_STATUS" != "ON" ]; then
    log_alert "Binary logging is disabled"
    exit 1
fi

# Check binary log file age
CURRENT_BINLOG=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW MASTER STATUS;" | tail -1 | awk '{print $1}')
if [ -n "$CURRENT_BINLOG" ]; then
    BINLOG_FILE="$BINLOG_DIR/$CURRENT_BINLOG"
    if [ -f "$BINLOG_FILE" ]; then
        BINLOG_AGE=$(stat -c %Y "$BINLOG_FILE")
        CURRENT_TIME=$(date +%s)
        AGE_HOURS=$(( (CURRENT_TIME - BINLOG_AGE) / 3600 ))
        
        if [ $AGE_HOURS -gt $MAX_BINLOG_AGE_HOURS ]; then
            log_alert "Current binary log is $AGE_HOURS hours old (max: $MAX_BINLOG_AGE_HOURS)"
        else
            log_info "Current binary log age: $AGE_HOURS hours"
        fi
    fi
fi

# Check backup directory size and age
if [ -d "$BACKUP_DIR" ]; then
    LATEST_BACKUP=$(find "$BACKUP_DIR" -type d -name "[0-9]*" | sort | tail -1)
    if [ -n "$LATEST_BACKUP" ]; then
        BACKUP_AGE=$(stat -c %Y "$LATEST_BACKUP")
        CURRENT_TIME=$(date +%s)
        BACKUP_AGE_HOURS=$(( (CURRENT_TIME - BACKUP_AGE) / 3600 ))
        
        if [ $BACKUP_AGE_HOURS -gt $MAX_BACKUP_AGE_HOURS ]; then
            log_alert "Latest backup is $BACKUP_AGE_HOURS hours old (max: $MAX_BACKUP_AGE_HOURS)"
        else
            log_info "Latest backup age: $BACKUP_AGE_HOURS hours"
        fi
        
        BACKUP_SIZE=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1)
        log_info "Total backup size: $BACKUP_SIZE"
    else
        log_alert "No backups found in $BACKUP_DIR"
    fi
else
    log_alert "Backup directory $BACKUP_DIR does not exist"
fi

# Check disk space
DISK_USAGE=$(df "$BACKUP_DIR" | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 90 ]; then
    log_alert "Backup disk usage is $DISK_USAGE% (threshold: 90%)"
else
    log_info "Backup disk usage: $DISK_USAGE%"
fi

log_info "Binary log monitoring completed"

Set up monitoring automation

Configure systemd timer for regular binary log and backup monitoring.

[Unit]
Description=MySQL Binary Log Monitor
After=mysql.service
Requires=mysql.service

[Service]
Type=oneshot
User=mysql
Group=mysql
ExecStart=/usr/local/bin/mysql-binlog-monitor.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=MySQL Binary Log Monitor Timer
Requires=mysql-binlog-monitor.service

[Timer]
OnCalendar=*-*-* *:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable monitoring automation

Start and enable the monitoring timer for continuous binary log health checks.

sudo chmod +x /usr/local/bin/mysql-binlog-monitor.sh
sudo systemctl daemon-reload
sudo systemctl enable mysql-binlog-monitor.timer
sudo systemctl start mysql-binlog-monitor.timer

Verify your setup

Test binary logging, backup automation, and recovery procedures to ensure everything works correctly.

# Verify binary logging is active
sudo mysql -e "SHOW VARIABLES LIKE 'log_bin';"
sudo mysql -e "SHOW BINARY LOGS;"

# Test backup script
sudo -u mysql /usr/local/bin/mysql-binlog-backup.sh

# Check backup directory
sudo ls -la /backup/mysql/binlogs/

# Test monitoring script
sudo -u mysql /usr/local/bin/mysql-binlog-monitor.sh

# Verify systemd timers
sudo systemctl status mysql-binlog-backup.timer
sudo systemctl status mysql-binlog-monitor.timer

# Check service logs
sudo journalctl -u mysql-binlog-backup.service -n 20
sudo journalctl -u mysql-binlog-monitor.service -n 20

Testing point-in-time recovery

Create test data and full backup

Generate test data and create a full backup to test the recovery process.

# Create test database and data
sudo mysql -e "CREATE DATABASE pitr_test;"
sudo mysql -e "USE pitr_test; CREATE TABLE test_data (id INT AUTO_INCREMENT PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, data VARCHAR(255));"
sudo mysql -e "USE pitr_test; INSERT INTO test_data (data) VALUES ('Initial data'), ('More data'), ('Test entry');"

# Create full backup
sudo mysqldump --single-transaction --routines --triggers --all-databases > /backup/mysql/full_backup_$(date +%Y%m%d_%H%M%S).sql

# Note the backup time
echo "Full backup completed at: $(date)"

# Add more test data after backup
sleep 5
sudo mysql -e "USE pitr_test; INSERT INTO test_data (data) VALUES ('After backup 1'), ('After backup 2');"
sleep 5
sudo mysql -e "USE pitr_test; INSERT INTO test_data (data) VALUES ('After backup 3'), ('Final entry');"

RECOVERY_TIME=$(date '+%Y-%m-%d %H:%M:%S')
echo "Recovery target time: $RECOVERY_TIME"

# Add data that should not be recovered
sleep 5
sudo mysql -e "USE pitr_test; INSERT INTO test_data (data) VALUES ('Should not appear'), ('After target time');"

# Show all data before recovery test
sudo mysql -e "USE pitr_test; SELECT * FROM test_data;"
Warning: Point-in-time recovery testing should only be performed on test systems or with proper backups of production data, as the recovery process replaces the current database state.

Common issues

Symptom Cause Fix
Binary logging not starting Insufficient disk space or permission issues Check /var/log/mysql/ permissions and disk space: sudo df -h and sudo ls -la /var/log/mysql/
Backup script fails with permission denied Incorrect file ownership or MySQL user lacks privileges Fix ownership: sudo chown mysql:mysql /backup/mysql/binlogs and verify MySQL user grants
Binary logs growing too large High transaction volume or infrequent log rotation Adjust max_binlog_size and expire_logs_days in MySQL config, run backup more frequently
Recovery fails with corrupt binary log Binary log file corruption or incomplete backup Use mysqlbinlog --verify-binlog-checksum to check integrity, restore from earlier backup if needed
Point-in-time recovery incomplete Missing binary logs or incorrect time format Ensure all binary logs between backup and target time exist, use format: YYYY-MM-DD HH:MM:SS
Monitoring script shows alerts Binary log backup not running or disk space issues Check timer status: sudo systemctl status mysql-binlog-backup.timer and free disk space

Next steps

Running this in production?

Want this handled for you? Running binary log backup at scale adds complexity: capacity planning for log growth, automated failover testing, cross-region replication, and 24/7 monitoring. See how we run infrastructure like this for European teams with strict recovery requirements.

Automated install script

Run this to automate the entire setup

Non vuoi gestirlo da solo?

Gestiamo l'infrastruttura di aziende che dipendono dall'uptime. Completamente gestita, con un referente fisso che conosce il tuo ambiente.

Avete un referente fisso che conosce il vostro ambiente

Rotterdam 05:58 · raggiungibile con un messaggio, senza modulo ticket