
August 13, 2026
10 min read
Table of Contents
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.
wp db export and system archive commands within a shell script triggered by server cron. This method creates timestamped SQL dumps and file archives independently of the WordPress admin, offering superior reliability and lower resource usage than traditional plugin-based solutions for production environments.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.
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
mysqldumpandtarinstead 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-pluginsand--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.
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.
- Create the backup directory:
sudo mkdir -p /var/backups/wordpress/mysite - Set ownership:
sudo chown -R www-data:www-data /var/backups/wordpress/mysite - Restrict access:
sudo chmod 700 /var/backups/wordpress/mysite - 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 Option | Best For | Cost (Approx.) | Complexity | Nepal Considerations |
|---|---|---|---|---|
| AWS S3 Glacier | Long-term archival, compliance | $0.004/GB/month | Medium | Requires USD payment method; retrieval takes hours |
| Cloudflare R2 | Frequent access, no egress fees | $0.015/GB/month | Low | No egress charges ideal for frequent restores; USD billing |
| Hetzner Storage Box | Budget bulk storage, EU location | €3.79/1TB/month | Low | Excellent value; supports SFTP/Borg; EUR billing |
| Secondary VPS (SFTP) | Full control, geo-redundancy | NPR 500–1000/month | High | Can use local Nepali host for second copy; NPR billing |
| Backblaze B2 | Balance of cost and speed | $0.005/GB/month | Medium | Good 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.
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:
- Extract files:
tar -xzf files-YYYYMMDD.tar.gz -C /var/www/html/ - Restore database:
gunzip < db-YYYYMMDD.sql.gz | wp db import - --path=/var/www/html/mysite - Reset permissions:
chown -R www-data:www-data /var/www/html/mysite - Flush caches:
wp cache flush --path=/var/www/html/mysite - 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.

