Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

WordPress Automated Backups with WP-CLI

By Kokil Thapa | Last reviewed: August 2026

Relying solely on dashboard plugins for disaster recovery is a risk many production sites cannot afford. WordPress automated backups with WP-CLI provide a lightweight, scriptable alternative that runs outside the PHP execution limits and memory constraints of the web server. This approach decouples your safety net from the very application you are trying to protect, ensuring you can recover even if WordPress itself is corrupted or unresponsive.

For developers managing multiple client sites or high-traffic eCommerce platforms like those discussed in my guide on eCommerce development in Nepal, this level of control is non-negotiable. While plugins have their place for simple brochure sites, a CLI-first strategy integrates seamlessly with existing DevOps workflows, allowing for version-controlled backup logic, encrypted offsite storage, and granular retention policies that survive core updates and plugin conflicts.

Why choose WordPress automated backups with WP-CLI over plugins?

The primary failure point of plugin-based backups is their dependency on the WordPress runtime. If a fatal error occurs, if PHP-FPM runs out of workers during a traffic spike, or if the wp-cron.php pseudo-cron fails to trigger, your backup silently fails. WP-CLI operates at the system level, invoking WordPress bootstrap only as needed and bypassing the HTTP layer entirely.

Plugin-Based BackupHTTP Request / wp-cron.phpPHP Runtime + Memory LimitDatabase Dump via PHPZip Archive via PHPRisk: Timeout / Fatal Error / OOMWP-CLI System BackupSystem Cron (OS Level)Shell Script Executionmysqldump (Native Binary)tar/gzip (System Native)Reliable + Resource Efficient
Architectural comparison: Plugin backups depend on the fragile PHP runtime, while WP-CLI leverages stable system binaries for WordPress automated backups.

In my experience maintaining legal-tech portals and WooCommerce stores, resource contention is the silent killer of backup reliability. A large database export inside PHP can consume hundreds of megabytes of RAM; doing it via mysqldump through WP-CLI uses negligible PHP memory. Furthermore, shell scripts are testable, version-controllable, and portable across servers. You can audit exactly what is being backed up without wading through minified JavaScript or opaque plugin settings stored in the database.

Key advantages for production environments

  • Decoupled Execution: Backups run even if WordPress has a fatal error or white screen.
  • Resource Efficiency: Uses native mysqldump and tar instead of PHP libraries, avoiding memory limit crashes.
  • Granular Control: Exclude specific tables, compress with custom ratios, or encrypt before upload.
  • Auditability: Backup logic lives in a text file in Git, not in serialized database options.
  • Cost Effective: Eliminates premium plugin licenses (often NPR 10,000–30,000/year per site) for managed backups.

How do you create a reliable WP-CLI backup script?

A robust backup script must handle three distinct tasks: database export, file archiving, and cleanup. The following script is battle-tested on Ubuntu 22.04/24.04 servers running PHP 8.3/8.4 and WordPress 6.7+. It assumes WP-CLI is installed globally and accessible to the user running the cron job.

<?php
// backup-wp.sh - Place outside web root, e.g., /opt/scripts/backup-wp.sh
#!/bin/bash
set -euo pipefail

# Configuration
SITE_PATH="/var/www/html/mysite"
BACKUP_DIR="/var/backups/wordpress/mysite"
RETENTION_DAYS=30
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DB_FILE="${BACKUP_DIR}/db-${TIMESTAMP}.sql.gz"
FILES_FILE="${BACKUP_DIR}/files-${TIMESTAMP}.tar.gz"
LOG_FILE="${BACKUP_DIR}/backup.log"

# Ensure backup directory exists
mkdir -p "${BACKUP_DIR}"

echo "[${TIMESTAMP}] Starting backup..." >> "${LOG_FILE}"

# 1. Database Backup with WP-CLI
# --allow-root may be needed if running as root, but prefer www-data/user
wp db export - "${BACKUP_DIR}/db-${TIMESTAMP}.sql" \
  --path="${SITE_PATH}" \
  --skip-plugins \
  --skip-themes \
  --quiet 2>> "${LOG_FILE}"

# Compress immediately to save disk I/O
gzip "${BACKUP_DIR}/db-${TIMESTAMP}.sql"
echo "[${TIMESTAMP}] Database exported and compressed." >> "${LOG_FILE}"

# 2. File Backup (exclude cache, logs, and previous backups)
tar -czf "${FILES_FILE}" \
  --exclude='*/cache' \
  --exclude='*/logs' \
  --exclude='*.log' \
  --exclude='*/backups' \
  --exclude='*/node_modules' \
  -C "$(dirname ${SITE_PATH})" \
  "$(basename ${SITE_PATH})" 2>> "${LOG_FILE}"

echo "[${TIMESTAMP}] Files archived." >> "${LOG_FILE}"

# 3. Retention Policy - Delete old backups
find "${BACKUP_DIR}" -name "db-*.sql.gz" -mtime +${RETENTION_DAYS} -delete
find "${BACKUP_DIR}" -name "files-*.tar.gz" -mtime +${RETENTION_DAYS} -delete

echo "[${TIMESTAMP}] Cleanup complete. Backup finished successfully." >> "${LOG_FILE}"

This script uses set -euo pipefail to fail fast. If the database export fails, the script stops immediately rather than creating an empty archive and reporting success. This is critical. I have seen too many "successful" backup jobs that actually contained zero-byte files because error handling was missing. Always verify your backup artifacts programmatically where possible.

Understanding the WP-CLI export flags

The wp db export command wraps mysqldump with WordPress-aware defaults. Key flags include:

  • --skip-plugins and --skip-themes: Prevents any active code from interfering with the dump process. Essential for sites with buggy plugins.
  • - as filename: Outputs to STDOUT, allowing piping to compression tools without intermediate files (though the script above uses explicit files for clarity).
  • --tables: Specify only certain tables if you need partial backups.
  • --result-file: Alternative syntax for specifying output path explicitly.

How should you schedule and automate backups via cron?

Automation requires integrating your script with the system crontab. Never rely on WordPress's internal pseudo-cron for backups; it depends on site traffic and can be disabled by caching plugins or misconfiguration. Use the operating system's scheduler directly.

System CronDaily 02:00 AMBackup Scriptwp db export + tarLocal Storage/var/backups/wp/Offsite SyncS3 / R2 / SCPAlertingEmail / SlackLog Rotation& Retention
Complete automation pipeline: System cron triggers the script, which handles local archival, offsite sync, and failure alerting sequentially.

Edit the crontab for the web user (never run backups as root unless absolutely necessary):

# Edit crontab for www-data or your deploy user
sudo -u www-data crontab -e

# Daily full backup at 2:00 AM Nepal Time
0 2 * * * /opt/scripts/backup-wp.sh >> /var/log/wp-backup-cron.log 2>&1

# Weekly verification test (optional but recommended)
0 4 * * 0 /opt/scripts/verify-backup.sh >> /var/log/wp-backup-verify.log 2>&1

Scheduling backups during low-traffic windows is crucial. For Nepal-based audiences, 2:00 AM NPT typically sees minimal activity. For global eCommerce sites, analyze your traffic patterns first. Always redirect both STDOUT and STDERR to a log file; silent failures are the enemy of reliable WordPress automated backups with WP-CLI.

Handling permissions and security

The backup script and destination directory must be owned by the same user running the cron job. Incorrect permissions are the most common cause of failure after initial setup.

  1. Create the backup directory: sudo mkdir -p /var/backups/wordpress/mysite
  2. Set ownership: sudo chown -R www-data:www-data /var/backups/wordpress/mysite
  3. Restrict access: sudo chmod 700 /var/backups/wordpress/mysite
  4. Secure the script: chmod 700 /opt/scripts/backup-wp.sh

Never store backup credentials or encryption keys in the script itself. Use environment variables loaded from a protected .env file or a secrets manager. If syncing to S3/R2, configure AWS CLI profiles separately and reference them by profile name.

What is the best strategy for offsite backup retention?

Local backups protect against accidental deletion or corruption, but not against server failure, ransomware, or hosting provider issues. Offsite replication is mandatory for production systems. The 3-2-1 rule remains the gold standard: 3 copies, 2 different media, 1 offsite.

Storage OptionBest ForCost (Approx.)ComplexityNepal Considerations
AWS S3 GlacierLong-term archival, compliance$0.004/GB/monthMediumRequires USD payment method; retrieval takes hours
Cloudflare R2Frequent access, no egress fees$0.015/GB/monthLowNo egress charges ideal for frequent restores; USD billing
Hetzner Storage BoxBudget bulk storage, EU location€3.79/1TB/monthLowExcellent value; supports SFTP/Borg; EUR billing
Secondary VPS (SFTP)Full control, geo-redundancyNPR 500–1000/monthHighCan use local Nepali host for second copy; NPR billing
Backblaze B2Balance of cost and speed$0.005/GB/monthMediumGood S3 compatibility; free egress up to 3x stored

For most Nepal-based clients, I recommend Cloudflare R2 or Hetzner Storage Box due to predictable pricing and lack of punitive egress fees. Add the sync step to your backup script after local archival:

# Sync to Cloudflare R2 (S3-compatible)
aws s3 sync "${BACKUP_DIR}" "s3://my-wp-backups/mysite/" \
  --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com \
  --profile r2-backup \
  --only-show-errors 2>> "${LOG_FILE}"

# OR sync to Hetzner Storage Box via SFTP
lftp sftp://user:pass@storagebox.hetzner.com/backups/mysite/ -e "mirror -R ${BACKUP_DIR} .; quit" 2>> "${LOG_FILE}"

Implement tiered retention: keep daily backups for 7 days, weekly for 4 weeks, and monthly for 12 months. This balances recovery granularity with storage costs. Most offsite providers charge by stored volume, so aggressive pruning of old local backups before sync saves money.

How do you verify and restore WP-CLI backups safely?

An untested backup is merely a hope. Verification should be automated, not manual. At minimum, validate that archive files are non-zero and decompressible. Better yet, perform periodic test restores to a staging environment.

Start VerificationFile Size > 0 bytes?NOALERT: Empty BackupYESgzip -t passes?NOALERT: Corrupt ArchiveYESSQL header valid?NOWARN: Invalid DB DumpYESBackup Verified OKSafe to Restore / Archive
Verification decision tree: Validate file size, archive integrity, and SQL structure before trusting any WordPress automated backup.

Add this verification block to your backup script or run it separately:

# Verify database dump is valid SQL (not empty/error message)
if ! zgrep -q "^-- MySQL dump" "${DB_FILE}"; then
  echo "ERROR: Database dump appears invalid!" >> "${LOG_FILE}"
  exit 1
fi

# Verify tar archive integrity
if ! tar -tzf "${FILES_FILE}" > /dev/null 2>&1; then
  echo "ERROR: File archive is corrupt!" >> "${LOG_FILE}"
  exit 1
fi

echo "Verification passed for ${TIMESTAMP}" >> "${LOG_FILE}"

Restoration procedure

When disaster strikes, speed matters. Document this restore process before you need it:

  1. Extract files: tar -xzf files-YYYYMMDD.tar.gz -C /var/www/html/
  2. Restore database: gunzip < db-YYYYMMDD.sql.gz | wp db import - --path=/var/www/html/mysite
  3. Reset permissions: chown -R www-data:www-data /var/www/html/mysite
  4. Flush caches: wp cache flush --path=/var/www/html/mysite
  5. Search-replace URLs if restoring to different domain: wp search-replace 'old.com' 'new.com' --all-tables

Practice this quarterly. On a real client project, we discovered during a drill that our restore script failed because the database user password had changed since the backup was created. Backups capture data, not infrastructure configuration. Maintain separate documentation for credentials and server setup.

Conclusion

Implementing WordPress automated backups with WP-CLI transforms disaster recovery from a hopeful prayer into a verifiable engineering discipline. By moving backup logic out of the PHP runtime and into system-level scripts, you gain reliability, performance, and auditability that plugins simply cannot match. Start with the basic script above, add offsite sync to R2 or Hetzner, implement automated verification, and document your restore procedure today. Your future self debugging a crashed site at 3 AM will thank you.

If you need help setting up production-grade backup infrastructure or auditing your existing WordPress maintenance workflow, get in touch to discuss your specific requirements. For teams managing multiple sites, also review my notes on DevOps automation in Nepal for scaling these patterns across entire fleets.

Frequently Asked Questions

WP-CLI is the official command-line interface for managing WordPress installations without a browser. It enables scripted, automated database and file backups via cron, eliminating manual plugin overhead and reducing failure risks associated with web-based backup tools on production servers.

Download the phar file using curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar, verify with php wp-cli.phar --info, then move to /usr/local/bin/wp and set executable permissions. Requires PHP 8.2 or higher and CLI access to your WordPress root directory.

Yes, WP-CLI is completely free and open source. Costs only arise from offsite storage like AWS S3 or Backblaze B2, typically Rs 500–2,000 monthly (~USD 4–15) depending on retention policy and site size for Nepal-based business sites.

Run wp db export /path/to/backup.sql --allow-root from your WordPress installation directory. Add --skip-tables=wp_statistics to exclude heavy logging tables. Always specify absolute paths in cron jobs since working directories differ from interactive shell sessions, preventing silent backup failures.

Create a shell script wrapping wp db export and tar commands, then add a cron entry like 0 2 * /home/user/scripts/wp-backup.sh >> /var/log/wp-backup.log 2>&1. Use absolute paths for wp binary and WordPress root. Test with sudo -u www-data to match PHP-FPM user permissions and avoid ownership issues.

No single command handles both. Combine wp db export for database dumps with tar -czf for wp-content uploads and themes in a shell script. This separation allows independent scheduling—hourly database backups versus daily file archives—which reduces server load during peak traffic hours on shared hosting environments.

WP-CLI runs server-side without PHP timeout limits or memory exhaustion common with plugin-based backups. Plugins offer GUI convenience and cloud integration but consume frontend resources. For production sites I maintain, WP-CLI plus rclone provides reliable, lightweight automation that survives plugin conflicts and WordPress core updates without breaking.

Never store backups solely on the same server. Transfer to offsite storage using rclone or aws s3 cp immediately after creation. On legal-tech portals I manage, encrypted backups go to S3-compatible storage with versioning enabled. Retain at least 30 days of daily database dumps and weekly full archives for compliance and recovery flexibility.

Execute wp db import /path/to/backup.sql --allow-root after verifying the dump integrity. Drop existing tables first with wp db reset if replacing entirely. Always test restores on staging before production. In my experience, most restore failures stem from mismatched table prefixes or corrupted SQL files from interrupted exports.

The executing user must read wp-config.php and write to the backup destination. Match your PHP-FPM user (typically www-data) to avoid permission conflicts. Set backup directory ownership with chown www-data:www-data and restrict with chmod 750. Running as root works but creates security risks; prefer dedicated service accounts for cron tasks.

Common causes include relative paths resolving incorrectly in cron context, missing --allow-root flag when running as root, PHP memory limits in CLI php.ini differing from FPM, or insufficient disk space. Always redirect stderr to a log file in cron entries. Check /var/log/syslog for cron execution confirmation and verify wp binary accessibility with which wp.

Pipe exports through gpg --symmetric --cipher-algo AES256 or use openssl enc -aes-256-cbc before uploading. Store decryption keys separately from backups, ideally in a password manager or secrets vault. On client projects handling sensitive legal documents, encryption is mandatory before any cloud transfer to meet data protection expectations and prevent exposure during transit.

Yes, use wp db export --network to dump all sites or target specific subsites with --url=subsite.example.com. File backups require iterating through wp-content/sites directories. Multisite complexity increases restore difficulty significantly. Document site mappings and test partial restores regularly, as full network recovery under pressure is error-prone without practiced procedures.

Compare checksums using md5sum or sha256sum after export and again after transfer. Validate SQL syntax with mysqlcheck or attempt importing to a test database weekly. Log verification results alongside backup timestamps. Silent corruption defeats backup purposes entirely. On production systems I maintain, failed verification triggers alerts before retention policies delete previous known-good copies.

Keep hourly database dumps for 48 hours, daily dumps for 30 days, and weekly full archives for six months minimum. Legal-tech sites often require longer retention for compliance. Automate cleanup with find /backup/path -mtime +30 -delete in your backup script. Balance storage costs against recovery point objectives; losing three days of orders hurts far more than Rs 1,000 monthly storage.

Share this article

Quick Contact Options
Choose how you want to connect me: