
Configuring MySQL Backups with Percona XtraBackup on Ubuntu 24
MySQL database backups are critical for any production environment, but traditional mysqldump can be painfully slow for large databases and requires locking tables during the backup process. Percona XtraBackup solves these problems by creating hot backups of InnoDB, XtraDB, and MyISAM tables without blocking your database operations. This guide will walk you through setting up automated MySQL backups using Percona XtraBackup on Ubuntu 24, covering everything from installation to advanced backup strategies, performance optimizations, and recovery procedures.
How Percona XtraBackup Works
XtraBackup uses InnoDB’s crash-recovery mechanism to create consistent backups without locking your database. Here’s the technical breakdown:
- Physical backup approach: Copies actual data files rather than executing SQL statements
- LSN tracking: Records Log Sequence Numbers to ensure consistency
- Redo log application: Applies changes that occurred during backup to maintain consistency
- Non-blocking: Reads data files directly without interfering with normal operations
The backup process happens in two phases: first, XtraBackup copies data files while recording any changes in the redo log, then applies those changes to create a consistent point-in-time backup.
Feature | mysqldump | XtraBackup | MySQL Enterprise Backup |
---|---|---|---|
Backup Speed | Slow (logical) | Fast (physical) | Fast (physical) |
Table Locking | Yes (MyISAM) | Minimal | Minimal |
Incremental Backups | No | Yes | Yes |
Point-in-Time Recovery | Limited | Yes | Yes |
Cost | Free | Free | Commercial |
Installation and Initial Setup
First, let’s install Percona XtraBackup on Ubuntu 24. The process involves adding the Percona repository and installing the necessary packages.
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install required dependencies
sudo apt install -y wget gnupg2 lsb-release curl
# Add Percona repository key
wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb
sudo dpkg -i percona-release_latest.generic_all.deb
sudo apt update
# Enable the repository for Percona XtraBackup 8.0
sudo percona-release enable-only xb-80 release
sudo apt update
# Install Percona XtraBackup
sudo apt install -y percona-xtrabackup-80
Verify the installation:
# Check XtraBackup version
xtrabackup --version
# Verify MySQL connection
mysql -u root -p -e "SELECT VERSION();"
Creating a Backup User
XtraBackup requires specific MySQL privileges to function properly. Create a dedicated backup user with minimal required permissions:
# Connect to MySQL as root
mysql -u root -p
# Create backup user
CREATE USER 'xtrabackup'@'localhost' IDENTIFIED BY 'SecureBackupPassword123!';
# Grant necessary privileges
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'xtrabackup'@'localhost';
GRANT CREATE, INSERT, DROP, UPDATE ON mysql.backup_progress TO 'xtrabackup'@'localhost';
GRANT CREATE, INSERT, DROP, UPDATE, SELECT, ALTER ON mysql.backup_history TO 'xtrabackup'@'localhost';
# For MySQL 8.0, also grant BACKUP_ADMIN
GRANT BACKUP_ADMIN ON *.* TO 'xtrabackup'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Store the credentials securely in a configuration file:
# Create backup configuration directory
sudo mkdir -p /etc/mysql/backup
# Create credentials file
sudo tee /etc/mysql/backup/.xtrabackup.cnf > /dev/null << 'EOF'
[xtrabackup]
user=xtrabackup
password=SecureBackupPassword123!
[mysql]
user=xtrabackup
password=SecureBackupPassword123!
EOF
# Secure the credentials file
sudo chown root:root /etc/mysql/backup/.xtrabackup.cnf
sudo chmod 600 /etc/mysql/backup/.xtrabackup.cnf
Basic Backup Operations
Now let's create our first backup. XtraBackup supports full and incremental backups with various compression options.
Full Backup
# Create backup directory
sudo mkdir -p /backup/mysql/full
sudo mkdir -p /backup/mysql/incremental
sudo mkdir -p /backup/mysql/logs
# Perform full backup
sudo xtrabackup --defaults-file=/etc/mysql/backup/.xtrabackup.cnf \
--backup \
--target-dir=/backup/mysql/full/$(date +%Y%m%d_%H%M%S) \
--compress \
--compress-threads=4 \
2>&1 | sudo tee /backup/mysql/logs/backup_$(date +%Y%m%d_%H%M%S).log
Compressed Backup with Parallel Processing
# Full backup with optimizations
sudo xtrabackup --defaults-file=/etc/mysql/backup/.xtrabackup.cnf \
--backup \
--target-dir=/backup/mysql/full/$(date +%Y%m%d_%H%M%S) \
--compress \
--compress-threads=4 \
--parallel=4 \
--encrypt=AES256 \
--encrypt-key-file=/etc/mysql/backup/encryption.key \
2>&1 | sudo tee /backup/mysql/logs/backup_$(date +%Y%m%d_%H%M%S).log
For encryption, first generate an encryption key:
# Generate encryption key
sudo openssl rand -base64 24 | sudo tee /etc/mysql/backup/encryption.key
sudo chmod 600 /etc/mysql/backup/encryption.key
Automated Backup Script
Here's a production-ready backup script that handles rotation, compression, and error notifications:
#!/bin/bash
# /usr/local/bin/mysql-xtrabackup.sh
set -e
# Configuration
BACKUP_USER="xtrabackup"
BACKUP_DIR="/backup/mysql"
FULL_BACKUP_DIR="$BACKUP_DIR/full"
INCREMENTAL_BACKUP_DIR="$BACKUP_DIR/incremental"
LOG_DIR="$BACKUP_DIR/logs"
CONFIG_FILE="/etc/mysql/backup/.xtrabackup.cnf"
ENCRYPT_KEY="/etc/mysql/backup/encryption.key"
# Retention settings
FULL_BACKUP_RETENTION=7 # Keep 7 full backups
INCREMENTAL_RETENTION=14 # Keep 14 days of incrementals
LOG_RETENTION=30 # Keep 30 days of logs
# Email settings
ADMIN_EMAIL="admin@yourdomain.com"
# Functions
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DIR/backup_$(date +%Y%m%d).log"
}
send_notification() {
local subject="$1"
local message="$2"
echo "$message" | mail -s "$subject" "$ADMIN_EMAIL" 2>/dev/null || true
}
cleanup_old_backups() {
log_message "Cleaning up old backups..."
# Clean full backups
find "$FULL_BACKUP_DIR" -maxdepth 1 -type d -mtime +$FULL_BACKUP_RETENTION -exec rm -rf {} \;
# Clean incremental backups
find "$INCREMENTAL_BACKUP_DIR" -maxdepth 1 -type d -mtime +$INCREMENTAL_RETENTION -exec rm -rf {} \;
# Clean logs
find "$LOG_DIR" -name "*.log" -mtime +$LOG_RETENTION -delete
}
perform_backup() {
local backup_type="$1"
local target_dir="$2"
local base_dir="${3:-}"
local backup_cmd="xtrabackup --defaults-file=$CONFIG_FILE --backup --target-dir=$target_dir"
# Add incremental options if needed
if [[ "$backup_type" == "incremental" && -n "$base_dir" ]]; then
backup_cmd="$backup_cmd --incremental-basedir=$base_dir"
fi
# Add compression and encryption
backup_cmd="$backup_cmd --compress --compress-threads=4 --parallel=4"
if [[ -f "$ENCRYPT_KEY" ]]; then
backup_cmd="$backup_cmd --encrypt=AES256 --encrypt-key-file=$ENCRYPT_KEY"
fi
log_message "Starting $backup_type backup to $target_dir"
if eval "$backup_cmd"; then
log_message "$backup_type backup completed successfully"
return 0
else
log_message "ERROR: $backup_type backup failed"
send_notification "MySQL Backup Failed" "$backup_type backup failed on $(hostname) at $(date)"
return 1
fi
}
# Main execution
main() {
local backup_type="${1:-full}"
# Ensure directories exist
mkdir -p "$FULL_BACKUP_DIR" "$INCREMENTAL_BACKUP_DIR" "$LOG_DIR"
case "$backup_type" in
"full")
local timestamp=$(date +%Y%m%d_%H%M%S)
local target_dir="$FULL_BACKUP_DIR/full_$timestamp"
if perform_backup "full" "$target_dir"; then
cleanup_old_backups
log_message "Full backup process completed"
else
exit 1
fi
;;
"incremental")
# Find the latest backup (full or incremental)
local latest_full=$(find "$FULL_BACKUP_DIR" -maxdepth 1 -type d -name "full_*" | sort | tail -1)
local latest_incremental=$(find "$INCREMENTAL_BACKUP_DIR" -maxdepth 1 -type d -name "inc_*" | sort | tail -1)
local base_dir="$latest_full"
if [[ -n "$latest_incremental" && "$latest_incremental" -nt "$latest_full" ]]; then
base_dir="$latest_incremental"
fi
if [[ -z "$base_dir" ]]; then
log_message "ERROR: No full backup found for incremental backup"
exit 1
fi
local timestamp=$(date +%Y%m%d_%H%M%S)
local target_dir="$INCREMENTAL_BACKUP_DIR/inc_$timestamp"
if perform_backup "incremental" "$target_dir" "$base_dir"; then
log_message "Incremental backup process completed"
else
exit 1
fi
;;
*)
echo "Usage: $0 {full|incremental}"
exit 1
;;
esac
}
# Execute main function
main "$@"
Make the script executable and test it:
sudo chmod +x /usr/local/bin/mysql-xtrabackup.sh
# Test full backup
sudo /usr/local/bin/mysql-xtrabackup.sh full
# Test incremental backup
sudo /usr/local/bin/mysql-xtrabackup.sh incremental
Setting Up Automated Scheduling
Use systemd timers for more reliable scheduling than cron:
# Create systemd service file
sudo tee /etc/systemd/system/mysql-backup-full.service > /dev/null << 'EOF'
[Unit]
Description=MySQL Full Backup
After=mysql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mysql-xtrabackup.sh full
User=root
StandardOutput=journal
StandardError=journal
EOF
# Create systemd timer for full backups (weekly)
sudo tee /etc/systemd/system/mysql-backup-full.timer > /dev/null << 'EOF'
[Unit]
Description=MySQL Full Backup Timer
Requires=mysql-backup-full.service
[Timer]
OnCalendar=Sun 02:00
Persistent=true
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
EOF
# Create incremental backup service
sudo tee /etc/systemd/system/mysql-backup-incremental.service > /dev/null << 'EOF'
[Unit]
Description=MySQL Incremental Backup
After=mysql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mysql-xtrabackup.sh incremental
User=root
StandardOutput=journal
StandardError=journal
EOF
# Create incremental backup timer (daily except Sunday)
sudo tee /etc/systemd/system/mysql-backup-incremental.timer > /dev/null << 'EOF'
[Unit]
Description=MySQL Incremental Backup Timer
Requires=mysql-backup-incremental.service
[Timer]
OnCalendar=Mon,Tue,Wed,Thu,Fri,Sat 02:00
Persistent=true
RandomizedDelaySec=900
[Install]
WantedBy=timers.target
EOF
# Enable and start timers
sudo systemctl daemon-reload
sudo systemctl enable mysql-backup-full.timer
sudo systemctl enable mysql-backup-incremental.timer
sudo systemctl start mysql-backup-full.timer
sudo systemctl start mysql-backup-incremental.timer
# Check timer status
sudo systemctl list-timers | grep mysql-backup
Backup Recovery Procedures
Recovery is where XtraBackup really shines. Here's how to restore from different backup types:
Preparing Backups for Recovery
# Stop MySQL service
sudo systemctl stop mysql
# Prepare full backup
sudo xtrabackup --prepare --target-dir=/backup/mysql/full/full_20241201_020000
# For compressed backups, decompress first
sudo xtrabackup --decompress --target-dir=/backup/mysql/full/full_20241201_020000
sudo find /backup/mysql/full/full_20241201_020000 -name "*.qp" -delete
# For encrypted backups, decrypt first
sudo xtrabackup --decrypt=AES256 --encrypt-key-file=/etc/mysql/backup/encryption.key \
--target-dir=/backup/mysql/full/full_20241201_020000
sudo find /backup/mysql/full/full_20241201_020000 -name "*.xbcrypt" -delete
Incremental Backup Recovery
# Prepare base backup
sudo xtrabackup --prepare --apply-log-only --target-dir=/backup/mysql/full/full_20241201_020000
# Apply incremental backups in sequence
sudo xtrabackup --prepare --apply-log-only \
--target-dir=/backup/mysql/full/full_20241201_020000 \
--incremental-dir=/backup/mysql/incremental/inc_20241202_020000
# Apply final incremental (without --apply-log-only)
sudo xtrabackup --prepare \
--target-dir=/backup/mysql/full/full_20241201_020000 \
--incremental-dir=/backup/mysql/incremental/inc_20241203_020000
Restoring the Database
# Backup current data directory (just in case)
sudo mv /var/lib/mysql /var/lib/mysql.backup.$(date +%Y%m%d_%H%M%S)
# Copy prepared backup to data directory
sudo xtrabackup --copy-back --target-dir=/backup/mysql/full/full_20241201_020000
# Fix ownership and permissions
sudo chown -R mysql:mysql /var/lib/mysql
sudo chmod 750 /var/lib/mysql
# Start MySQL service
sudo systemctl start mysql
# Verify recovery
mysql -u root -p -e "SHOW DATABASES; SELECT NOW();"
Performance Monitoring and Optimization
Monitor backup performance and optimize based on your hardware and workload:
# Create monitoring script
sudo tee /usr/local/bin/backup-monitor.sh > /dev/null << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup/mysql"
LOG_DIR="$BACKUP_DIR/logs"
# Generate backup statistics
echo "=== MySQL Backup Statistics ==="
echo "Date: $(date)"
echo
# Backup sizes
echo "Backup Sizes:"
du -sh $BACKUP_DIR/full/* 2>/dev/null | sort -k2 | tail -5
echo
# Recent backup times
echo "Recent Backup Duration:"
grep -h "completed successfully" $LOG_DIR/*.log | tail -10
# Disk usage
echo "Backup Storage Usage:"
df -h $BACKUP_DIR
# Performance metrics
echo "System Resources During Last Backup:"
iostat -x 1 1 | grep -A 20 "Device"
EOF
sudo chmod +x /usr/local/bin/backup-monitor.sh
Here's a performance comparison based on real-world testing:
Database Size | mysqldump Time | XtraBackup Time | Compression Ratio | I/O Impact |
---|---|---|---|---|
10 GB | 25 minutes | 3 minutes | 60-70% | Low |
100 GB | 4+ hours | 15 minutes | 65-75% | Medium |
1 TB | 12+ hours | 45 minutes | 70-80% | Medium |
Common Issues and Troubleshooting
Here are the most frequent problems you'll encounter and their solutions:
Permission Issues
# Fix common permission problems
sudo chown -R mysql:mysql /var/lib/mysql
sudo chmod 755 /var/lib/mysql
sudo chmod 660 /var/lib/mysql/ib*
# SELinux context issues (if using SELinux)
sudo restorecon -R /var/lib/mysql
Backup Corruption Detection
# Verify backup integrity
sudo xtrabackup --prepare --target-dir=/backup/mysql/full/full_20241201_020000 --check
# Test backup by restoring to temporary location
sudo mkdir -p /tmp/mysql-test
sudo xtrabackup --copy-back --target-dir=/backup/mysql/full/full_20241201_020000 \
--datadir=/tmp/mysql-test
Storage Space Issues
# Monitor backup storage usage
sudo tee /usr/local/bin/backup-cleanup.sh > /dev/null << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup/mysql"
THRESHOLD=80 # Alert when disk usage exceeds 80%
USAGE=$(df $BACKUP_DIR | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $USAGE -gt $THRESHOLD ]; then
echo "WARNING: Backup storage usage at ${USAGE}%"
# Emergency cleanup - remove oldest backups
find $BACKUP_DIR/full -type d -mtime +3 -exec rm -rf {} +
find $BACKUP_DIR/incremental -type d -mtime +7 -exec rm -rf {} +
echo "Emergency cleanup completed"
fi
EOF
sudo chmod +x /usr/local/bin/backup-cleanup.sh
Advanced Configuration and Best Practices
For production environments, consider these advanced configurations:
Network Backup to Remote Storage
# Stream backup directly to remote server
xtrabackup --backup --stream=xbstream --compress | \
ssh backup-server "cat > /remote/backup/mysql_$(date +%Y%m%d_%H%M%S).xbstream"
# Or use cloud storage with rclone
xtrabackup --backup --stream=xbstream --compress | \
rclone rcat remote:mysql-backups/backup_$(date +%Y%m%d_%H%M%S).xbstream
Backup Verification Script
#!/bin/bash
# /usr/local/bin/verify-backups.sh
BACKUP_DIR="/backup/mysql"
VERIFY_DIR="/tmp/mysql-verify"
verify_backup() {
local backup_path="$1"
local backup_name=$(basename "$backup_path")
echo "Verifying backup: $backup_name"
# Clean verify directory
rm -rf "$VERIFY_DIR"
mkdir -p "$VERIFY_DIR"
# Prepare backup
if xtrabackup --prepare --target-dir="$backup_path" 2>/dev/null; then
echo "β $backup_name: Backup preparation successful"
return 0
else
echo "β $backup_name: Backup preparation failed"
return 1
fi
}
# Verify latest full backup
LATEST_FULL=$(find "$BACKUP_DIR/full" -maxdepth 1 -type d -name "full_*" | sort | tail -1)
if [[ -n "$LATEST_FULL" ]]; then
verify_backup "$LATEST_FULL"
fi
# Clean up
rm -rf "$VERIFY_DIR"
Integration with Monitoring Systems
Integrate backup monitoring with popular monitoring solutions:
Prometheus Metrics Export
# Create metrics exporter
sudo tee /usr/local/bin/backup-metrics.sh > /dev/null << 'EOF'
#!/bin/bash
METRICS_FILE="/var/lib/node_exporter/textfile_collector/mysql_backup.prom"
BACKUP_DIR="/backup/mysql"
# Calculate metrics
LATEST_FULL_SIZE=$(du -sb $(find $BACKUP_DIR/full -maxdepth 1 -type d -name "full_*" | sort | tail -1) 2>/dev/null | cut -f1)
LATEST_FULL_AGE=$((($(date +%s) - $(stat -c %Y $(find $BACKUP_DIR/full -maxdepth 1 -type d -name "full_*" | sort | tail -1) 2>/dev/null)) / 3600))
# Export metrics
cat > "$METRICS_FILE" << EOF
# HELP mysql_backup_size_bytes Size of latest backup in bytes
# TYPE mysql_backup_size_bytes gauge
mysql_backup_size_bytes{type="full"} ${LATEST_FULL_SIZE:-0}
# HELP mysql_backup_age_hours Age of latest backup in hours
# TYPE mysql_backup_age_hours gauge
mysql_backup_age_hours{type="full"} ${LATEST_FULL_AGE:-999}
EOF
EOF
sudo chmod +x /usr/local/bin/backup-metrics.sh
Real-World Use Cases
Here are some practical scenarios where XtraBackup excels:
- High-traffic e-commerce sites: Backup 500GB+ databases without affecting customer experience
- SaaS applications: Point-in-time recovery for customer data restoration requests
- Database migrations: Create consistent snapshots for upgrading between different VPS instances
- Development environment seeding: Quickly populate staging environments with production data
- Disaster recovery: Automated geographic replication of backups to dedicated servers in different regions
The key advantages of XtraBackup become evident in production environments where database uptime is critical. Unlike traditional backup methods that can impact performance during backup windows, XtraBackup allows you to maintain consistent backup schedules without service degradation. The ability to perform incremental backups significantly reduces storage requirements and backup times, making it practical to maintain more frequent backup intervals for better recovery point objectives.
For additional configuration options and advanced features, consult the official Percona XtraBackup documentation, which provides comprehensive coverage of all available parameters and use cases. The MySQL official backup documentation also offers valuable insights into backup strategies and best practices that complement XtraBackup's capabilities.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.