Losing a WordPress site to a server crash, hack, or accidental deletion is a nightmare no developer should experience. Yet according to WordPress security statistics, over 30,000 websites are hacked every day, and many have no recent backups.
- Why Automate WordPress Backups?
- What to Backup in WordPress
- Prerequisites
- Building the Backup Script Step-by-Step
- Database Backup with WP-CLI
- File System Backup
- Compression and Storage Optimization
- Retention Policies
- Email Notifications
- Scheduling with Cron
- Testing Your Backups
- Off-Site Backup Storage
- Complete Production Script
- Troubleshooting
- Next Steps
- Conclusion
Manual backups are tedious, error-prone, and easy to forget. The solution? Automated backups using WP-CLI and Bash that run like clockwork—whether you’re awake or asleep.
In this comprehensive guide, you’ll learn how to build a production-ready WordPress backup automation system from scratch. By the end, you’ll have a script that automatically backs up your database, files, compresses everything, manages retention policies, and even notifies you of success or failure.
Why Automate WordPress Backups?
The Cost of No Backups
Real scenario: A client’s WordPress site gets hacked at 3 AM. The attacker deletes the database and replaces files with malware. Without automated backups, you’re looking at:
- Hours of downtime (lost revenue)
- Lost content (months of blog posts, products, customer data)
- Damaged reputation (customers lose trust)
- Potential data breach liability
With automated backups, you restore from last night’s backup and are back online in 15 minutes.
Manual Backups Don’t Scale
The manual backup problem:
- Requires you to remember (you won’t always)
- Takes 10-15 minutes per site
- Easy to make mistakes (wrong directory, incomplete backup)
- Doesn’t work at 3 AM when you’re sleeping
Automated backups solve this:
- Run on schedule without human intervention
- Always complete (or notify you of failures)
- Work 24/7/365
- Scale to hundreds of sites with the same script
Statistics That Matter
According to CodeinWP’s WordPress statistics:
- 30% of websites have no backup at all
- 60% of small businesses never recover from data loss
- The average cost of downtime is $5,600 per minute for e-commerce sites
Can you afford NOT to automate backups?
What to Backup in WordPress
A complete WordPress backup includes two critical components:
1. Database (MySQL/MariaDB)
The database contains:
- All posts, pages, and custom post types
- Comments and user data
- Site settings and options
- Plugin and theme configurations
- Widgets and menus
Size: Usually 5-50 MB for small sites, up to 500 MB+ for large sites
Backup frequency: Daily minimum, hourly for high-traffic sites
2. File System
Essential files include:
- wp-content/uploads/ – All media files (images, videos, PDFs)
- wp-content/themes/ – Custom themes
- wp-content/plugins/ – Installed plugins
- wp-config.php – Database configuration (contains credentials)
- .htaccess – Server configuration
Size: Can range from 100 MB to 50+ GB depending on media library
Backup frequency: Daily for active sites, weekly for static sites
What You Can Skip
To save space and time, you can exclude:
- wp-content/cache/ – Temporary cache files
- wp-content/backups/ – Avoid backing up backups
- wp-core files – Can be re-downloaded from WordPress.org
For very large media libraries (10+ GB), consider backing up uploads separately to cloud storage.
Prerequisites
Before building the automation script, ensure you have:
Required Software
- WP-CLI installed – Installation guide
- Bash shell – Standard on Linux/Unix/macOS
- SSH access to your server
- Basic Bash knowledge – Bash functions guide
Server Requirements
- Disk space: At least 2x your WordPress installation size for backups
- Write permissions: Access to create backup directories
- WP-CLI access: Ability to run
wpcommands - Cron access: For scheduling (optional but recommended)
Test Your Setup
Verify WP-CLI works:
cd /var/www/html
wp core version
If this returns your WordPress version, you’re ready to proceed.
Building the Backup Script Step-by-Step
Let’s build the backup script incrementally, starting simple and adding features.
Step 1: Basic Structure
Create a new file:
nano ~/wp-backup.sh
Add the basic structure:
#!/bin/bash
#
# WordPress Backup Automation Script
# Backs up database and files for a WordPress installation
#
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Configuration
WP_PATH="/var/www/html"
BACKUP_DIR="/var/backups/wordpress"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
echo "Starting WordPress backup..."
echo "Timestamp: ${TIMESTAMP}"
# Script logic will go here
echo "Backup completed!"
Make it executable:
chmod +x ~/wp-backup.sh
What this does:
set -euo pipefailensures the script exits on errors- Variables store paths and timestamp
- Basic output shows script is running
Database Backup with WP-CLI
Export Database to SQL File
Add database backup function:
backup_database() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local db_file="${backup_dir}/database_${timestamp}.sql"
echo "Backing up database..."
if wp db export "$db_file" --path="$wp_path" --quiet; then
echo "✓ Database exported: ${db_file}"
echo "$db_file"
return 0
else
echo "✗ Database export failed" >&2
return 1
fi
}
How this works:
wp db exportdumps the entire database to a .sql file--quietsuppresses verbose output- Returns the filename on success, empty on failure
Add Compression
Compress the SQL file to save space:
backup_database() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local db_file="${backup_dir}/database_${timestamp}.sql"
echo "Backing up database..."
if wp db export "$db_file" --path="$wp_path" --quiet; then
echo "✓ Database exported: ${db_file}"
# Compress with gzip
gzip "$db_file"
echo "✓ Database compressed: ${db_file}.gz"
echo "${db_file}.gz"
return 0
else
echo "✗ Database export failed" >&2
return 1
fi
}
Compression benefits:
- Typical compression ratio: 10:1 (50 MB → 5 MB)
- Saves disk space and transfer bandwidth
- Standard format compatible with all restore tools
Database Backup Best Practices
Security considerations:
- SQL files contain database credentials in plain text
- Ensure backup directory has restricted permissions (700)
- Never store backups in web-accessible directories
# Secure backup directory
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
Performance tips:
- Large databases (500+ MB) can take 1-5 minutes to export
- Run backups during low-traffic periods (2-4 AM)
- Consider incremental backups for massive databases
File System Backup
Backup WordPress Files with tar
Add file backup function:
backup_files() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local files_archive="${backup_dir}/files_${timestamp}.tar.gz"
echo "Backing up files..."
# Exclude cache and temporary files
local exclude_opts="--exclude=wp-content/cache --exclude=wp-content/backup*"
if tar -czf "$files_archive" \
$exclude_opts \
-C "$(dirname $wp_path)" \
"$(basename $wp_path)" 2>/dev/null; then
echo "✓ Files archived: ${files_archive}"
echo "$files_archive"
return 0
else
echo "✗ File backup failed" >&2
return 1
fi
}
What this does:
tar -czfcreates a compressed archive-Cchanges directory before archiving--excludeskips cache and backup directories- Output is redirected to suppress tar warnings
Conditional Uploads Backup
For sites with large media libraries, make uploads optional:
backup_files() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local include_uploads=${4:-true} # Default to true
local files_archive="${backup_dir}/files_${timestamp}.tar.gz"
echo "Backing up files..."
# Build exclude options
local exclude_opts="--exclude=wp-content/cache --exclude=wp-content/backup*"
if [ "$include_uploads" = "false" ]; then
echo " (Excluding uploads directory)"
exclude_opts="${exclude_opts} --exclude=wp-content/uploads"
fi
if tar -czf "$files_archive" \
$exclude_opts \
-C "$(dirname $wp_path)" \
"$(basename $wp_path)" 2>/dev/null; then
echo "✓ Files archived: ${files_archive}"
echo "$files_archive"
return 0
else
echo "✗ File backup failed" >&2
return 1
fi
}
# Usage
backup_files /var/www/html /backups $TIMESTAMP false # Skip uploads
When to exclude uploads:
- Media library > 10 GB
- Uploads stored on CDN or cloud storage
- Backup uploads separately to S3/Backblaze
Compression and Storage Optimization
Compression Comparison
| Method | Ratio | Speed | CPU Usage |
|---|---|---|---|
| gzip (default) | 10:1 | Fast | Low |
| bzip2 | 15:1 | Slow | Medium |
| xz | 20:1 | Very Slow | High |
Recommendation: Use gzip for daily backups (best speed/size balance)
Combined Archive
Create a single archive containing both database and files:
create_combined_backup() {
local db_backup=$1
local files_backup=$2
local backup_dir=$3
local timestamp=$4
local combined="${backup_dir}/wordpress_backup_${timestamp}.tar.gz"
local temp_dir=$(mktemp -d)
# Copy both backups to temp directory
cp "$db_backup" "$files_backup" "$temp_dir/"
# Create combined archive
tar -czf "$combined" -C "$temp_dir" .
# Clean up
rm -rf "$temp_dir"
rm -f "$db_backup" "$files_backup"
echo "✓ Combined backup created: ${combined}"
echo "$combined"
}
Benefits:
- Single file to manage
- Easier to transfer and restore
- Atomic backup (all or nothing)
Retention Policies
Automatic Cleanup of Old Backups
Prevent backups from filling your disk:
cleanup_old_backups() {
local backup_dir=$1
local retention_days=${2:-7} # Default 7 days
echo "Cleaning up backups older than ${retention_days} days..."
local deleted=$(find "$backup_dir" \
-name "wordpress_backup_*.tar.gz" \
-type f \
-mtime +${retention_days} \
-delete -print | wc -l)
if [ "$deleted" -gt 0 ]; then
echo "✓ Deleted ${deleted} old backup(s)"
else
echo " No old backups to clean up"
fi
}
# Usage
cleanup_old_backups /var/backups/wordpress 7 # Keep 7 days
Retention Strategy Recommendations
Daily backups:
- Keep: Last 7 days
- Disk usage: ~7x backup size
Weekly backups:
- Keep: Last 4 weeks (28 days)
- Disk usage: ~4x backup size
Monthly backups:
- Keep: Last 12 months (365 days)
- Disk usage: ~12x backup size
3-2-1 Rule:
- 3 copies of data (original + 2 backups)
- 2 different storage types (local + cloud)
- 1 copy off-site
Email Notifications
Send Email on Success/Failure
send_notification() {
local subject=$1
local message=$2
local email="${NOTIFICATION_EMAIL:-}"
if [ -z "$email" ]; then
return 0 # No email configured
fi
if command -v mail &> /dev/null; then
echo -e "$message" | mail -s "$subject" "$email"
echo "✓ Email sent to ${email}"
else
echo "⚠ mail command not found (install mailutils)"
fi
}
# Usage on success
send_notification \
"WordPress Backup SUCCESS" \
"Backup completed successfully\nFile: ${backup_file}\nSize: ${size}"
# Usage on failure
send_notification \
"WordPress Backup FAILED" \
"Backup failed during database export\nCheck logs for details"
Install Mail Utilities
# Ubuntu/Debian
sudo apt-get install mailutils
# CentOS/RHEL
sudo yum install mailx
Configure email in script:
# At top of script
NOTIFICATION_EMAIL="admin@yourdomain.com"
Scheduling with Cron
Add to Crontab
Schedule daily backups at 2 AM:
# Edit crontab
crontab -e
# Add this line
0 2 * * * /home/username/wp-backup.sh >> /var/log/wp-backup.log 2>&1
Cron schedule breakdown:
0– Minute (0)2– Hour (2 AM)*– Day of month (every day)*– Month (every month)*– Day of week (every day)
Common Cron Schedules
# Every 6 hours
0 */6 * * * /home/username/wp-backup.sh
# Daily at 3:30 AM
30 3 * * * /home/username/wp-backup.sh
# Weekly on Sundays at 2 AM
0 2 * * 0 /home/username/wp-backup.sh
# Twice daily (6 AM and 6 PM)
0 6,18 * * * /home/username/wp-backup.sh
Use crontab.guru to generate cron schedules visually.
Verify Cron is Running
# List your cron jobs
crontab -l
# Check cron logs (Ubuntu/Debian)
grep CRON /var/log/syslog
# Check your backup logs
tail -f /var/log/wp-backup.log
Testing Your Backups
Critical truth: A backup you haven’t tested is worthless.
Test Restore Procedure
- Extract backup:
cd /var/backups/wordpress
tar -xzf wordpress_backup_20250220_020000.tar.gz
- Restore database:
gunzip database_20250220_020000.sql.gz
wp db import database_20250220_020000.sql --path=/var/www/html
- Restore files:
tar -xzf files_20250220_020000.tar.gz -C /var/www/
- Verify site: Visit your site and check that content is intact.
Test on Staging Environment
Best practice: Never test restores on production.
Create a staging environment:
# Copy WordPress to staging
cp -r /var/www/html /var/www/staging
# Update wp-config.php with staging database
wp config set DB_NAME staging_db --path=/var/www/staging
# Test restore there
wp db import backup.sql --path=/var/www/staging
Backup Verification Script
verify_backup() {
local backup_file=$1
echo "Verifying backup integrity..."
# Check file exists
if [ ! -f "$backup_file" ]; then
echo "✗ Backup file not found"
return 1
fi
# Check file size (should be > 1KB)
local size=$(stat -f%z "$backup_file" 2>/dev/null || stat -c%s "$backup_file")
if [ "$size" -lt 1024 ]; then
echo "✗ Backup file suspiciously small (${size} bytes)"
return 1
fi
# Test tar integrity
if tar -tzf "$backup_file" &>/dev/null; then
echo "✓ Backup archive is valid"
return 0
else
echo "✗ Backup archive is corrupted"
return 1
fi
}
Off-Site Backup Storage
Local backups protect against accidental deletion. Off-site backups protect against server failure, fires, and catastrophic events.
Option 1: AWS S3
# Install AWS CLI
sudo apt-get install awscli
# Configure credentials
aws configure
# Sync backups to S3
aws s3 sync /var/backups/wordpress/ s3://your-bucket/wordpress-backups/
Add to backup script:
# After backup completes
echo "Syncing to AWS S3..."
if aws s3 cp "$backup_file" s3://your-bucket/backups/; then
echo "✓ Uploaded to S3"
else
echo "✗ S3 upload failed" >&2
fi
S3 pricing: ~$0.023/GB/month (very affordable)
Option 2: Rsync to Remote Server
# Sync to remote server via SSH
rsync -avz /var/backups/wordpress/ \
user@backup-server.com:/backups/wordpress/
Add to backup script:
# After backup completes
echo "Syncing to remote server..."
if rsync -avz "$backup_file" user@backup-server.com:/backups/; then
echo "✓ Synced to remote server"
else
echo "✗ Remote sync failed" >&2
fi
Option 3: Backblaze B2 (Budget-Friendly)
Backblaze B2 costs 1/4 of S3 ($0.005/GB/month):
# Install B2 CLI
pip install b2
# Configure
b2 authorize-account <key_id> <app_key>
# Upload
b2 upload-file <bucket_name> "$backup_file" backups/$(basename $backup_file)
Learn more: Backblaze B2 Documentation
Complete Production Script
Here’s the full production-ready script with all features:
#!/bin/bash
#
# WordPress Backup Automation Script
# Complete production-ready version
#
set -euo pipefail
#############################################
# CONFIGURATION
#############################################
WP_PATH="/var/www/html"
BACKUP_DIR="/var/backups/wordpress"
RETENTION_DAYS=7
NOTIFICATION_EMAIL="admin@yourdomain.com"
INCLUDE_UPLOADS=true
#############################################
# COLORS
#############################################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
#############################################
# FUNCTIONS
#############################################
log_info() {
echo -e "${GREEN}[INFO]${NC} $*"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
backup_database() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local db_file="${backup_dir}/database_${timestamp}.sql"
log_info "Backing up database..."
if wp db export "$db_file" --path="$wp_path" --quiet; then
gzip "$db_file"
log_info "Database backed up: ${db_file}.gz"
echo "${db_file}.gz"
return 0
else
log_error "Database backup failed"
return 1
fi
}
backup_files() {
local wp_path=$1
local backup_dir=$2
local timestamp=$3
local files_archive="${backup_dir}/files_${timestamp}.tar.gz"
local exclude_opts="--exclude=wp-content/cache --exclude=wp-content/backup*"
if [ "$INCLUDE_UPLOADS" = "false" ]; then
exclude_opts="${exclude_opts} --exclude=wp-content/uploads"
fi
log_info "Backing up files..."
if tar -czf "$files_archive" $exclude_opts \
-C "$(dirname $wp_path)" "$(basename $wp_path)" 2>/dev/null; then
log_info "Files backed up: ${files_archive}"
echo "$files_archive"
return 0
else
log_error "File backup failed"
return 1
fi
}
create_combined_backup() {
local db_backup=$1
local files_backup=$2
local backup_dir=$3
local timestamp=$4
local combined="${backup_dir}/wordpress_backup_${timestamp}.tar.gz"
local temp_dir=$(mktemp -d)
cp "$db_backup" "$files_backup" "$temp_dir/"
tar -czf "$combined" -C "$temp_dir" .
rm -rf "$temp_dir" "$db_backup" "$files_backup"
log_info "Combined backup: ${combined}"
echo "$combined"
}
cleanup_old_backups() {
local backup_dir=$1
local retention_days=$2
log_info "Cleaning up backups older than ${retention_days} days..."
find "$backup_dir" -name "wordpress_backup_*.tar.gz" \
-type f -mtime +${retention_days} -delete
}
send_notification() {
local subject=$1
local message=$2
if [ -n "$NOTIFICATION_EMAIL" ] && command -v mail &> /dev/null; then
echo -e "$message" | mail -s "$subject" "$NOTIFICATION_EMAIL"
fi
}
#############################################
# MAIN
#############################################
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
log_info "========================================="
log_info "WordPress Backup Started"
log_info "========================================="
log_info "Path: ${WP_PATH}"
log_info "Timestamp: ${TIMESTAMP}"
mkdir -p "$BACKUP_DIR"
# Backup database
if ! db_backup=$(backup_database "$WP_PATH" "$BACKUP_DIR" "$TIMESTAMP"); then
send_notification "Backup FAILED" "Database backup failed"
exit 1
fi
# Backup files
if ! files_backup=$(backup_files "$WP_PATH" "$BACKUP_DIR" "$TIMESTAMP"); then
send_notification "Backup FAILED" "File backup failed"
exit 1
fi
# Create combined backup
final_backup=$(create_combined_backup "$db_backup" "$files_backup" "$BACKUP_DIR" "$TIMESTAMP")
# Cleanup old backups
cleanup_old_backups "$BACKUP_DIR" "$RETENTION_DAYS"
# Get backup size
backup_size=$(du -h "$final_backup" | cut -f1)
log_info "========================================="
log_info "Backup Completed Successfully!"
log_info "File: ${final_backup}"
log_info "Size: ${backup_size}"
log_info "========================================="
send_notification "Backup SUCCESS" "Backup: ${final_backup}\nSize: ${backup_size}"
exit 0
Download this script: 👉 Get the production-ready backup script free
Troubleshooting
Issue 1: “wp: command not found”
Problem: WP-CLI is not in the cron user’s PATH.
Solution: Use full path in cron:
# Instead of:
0 2 * * * ~/wp-backup.sh
# Use:
0 2 * * * /usr/local/bin/wp-backup.sh
Or add PATH to crontab:
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * ~/wp-backup.sh
Issue 2: Permission Denied
Problem: Backup script can’t write to backup directory.
Solution:
# Create directory with proper permissions
sudo mkdir -p /var/backups/wordpress
sudo chown $USER:$USER /var/backups/wordpress
chmod 700 /var/backups/wordpress
Issue 3: Disk Space Full
Problem: Backups fill up the disk.
Solution: Reduce retention or exclude uploads:
# Check disk usage
df -h
# Reduce retention to 3 days
RETENTION_DAYS=3
# Or exclude uploads
INCLUDE_UPLOADS=false
Issue 4: Backup Taking Too Long
Problem: Large sites take 30+ minutes to backup.
Solution:
- Exclude uploads (back up separately)
- Run during off-peak hours
- Use incremental backups for files
- Consider database replication for very large databases
Issue 5: Cron Job Not Running
Problem: Script works manually but not via cron.
Solution: Check cron logs and add logging:
# Add to crontab
0 2 * * * /home/user/wp-backup.sh >> /var/log/wp-backup.log 2>&1
# Check logs
tail -f /var/log/wp-backup.log
# Verify cron service is running
sudo systemctl status cron
Next Steps
Congratulations! You now have a production-ready WordPress backup automation system.
Enhance Your Backup System
- Schedule WP-CLI Tasks with Cron – Advanced scheduling techniques
- WordPress Multisite Network Management – Back up entire networks
- WordPress CI/CD Pipeline with GitHub Actions – Integrate backups into deployment
Related Guides
- Bash Functions for WordPress – Make your scripts more maintainable
- Error Handling in Bash Scripts – Handle failures gracefully
- Install WP-CLI – Get WP-CLI set up properly
Master WordPress Automation
Want to build advanced automation systems like:
- Automated blog publishing with AI and APIs
- Complete WordPress deployment pipelines
- Multi-site management at scale
Join the WPCLI Mastery waitlist and get:
- This complete backup script (production-ready)
- Advanced backup strategies (incremental, differential)
- Off-site storage integration templates
- Early bird pricing ($99 vs $199)
Conclusion
Automated WordPress backups are non-negotiable for any production site. With WP-CLI and Bash, you can build a bulletproof backup system in less than an hour that runs flawlessly for years.
Key takeaways:
- Automate backups to eliminate human error
- Backup both database and files
- Compress backups to save space
- Implement retention policies to manage disk usage
- Test restores regularly (untested backups are useless)
- Store backups off-site for disaster recovery
- Monitor with email notifications
The script you built today can protect hundreds of WordPress sites. Set it up once, and sleep better knowing your data is safe.
Ready to implement? Download the complete production script and get it running today.
Questions about WordPress backup automation? Drop a comment below!
Found this helpful? Share it with other WordPress developers who need automated backups.
Next: Learn how to deploy WordPress environments automatically using similar automation techniques.

Leave a Reply