Automate WordPress Backups with WP-CLI and Bash (Step-by-Step)

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.

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

  1. WP-CLI installed – Installation guide
  2. Bash shell – Standard on Linux/Unix/macOS
  3. SSH access to your server
  4. 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 wp commands
  • 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 pipefail ensures 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 export dumps the entire database to a .sql file
  • --quiet suppresses 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 -czf creates a compressed archive
  • -C changes directory before archiving
  • --exclude skips 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

MethodRatioSpeedCPU Usage
gzip (default)10:1FastLow
bzip215:1SlowMedium
xz20:1Very SlowHigh

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

  1. Extract backup:
cd /var/backups/wordpress
tar -xzf wordpress_backup_20250220_020000.tar.gz
  1. Restore database:
gunzip database_20250220_020000.sql.gz
wp db import database_20250220_020000.sql --path=/var/www/html
  1. Restore files:
tar -xzf files_20250220_020000.tar.gz -C /var/www/
  1. 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:

  1. Exclude uploads (back up separately)
  2. Run during off-peak hours
  3. Use incremental backups for files
  4. 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

  1. Schedule WP-CLI Tasks with Cron – Advanced scheduling techniques
  2. WordPress Multisite Network Management – Back up entire networks
  3. WordPress CI/CD Pipeline with GitHub Actions – Integrate backups into deployment

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

Your email address will not be published. Required fields are marked *