Home › Blog › Article

WordPress Backup Strategy: 3-2-1 and Restoring

WordPress Backup Strategy: 3-2-1 and Restoring

A working WordPress backup strategy means three total copies of your site, on two different media, with one copy offsite, and proof you can restore. You are done when a fresh clone rebuilds from your offsite archive on a clean host in a timed drill, with database integrity checked and the site serving 200 responses. Verification is the restore itself, not the backup log.

What to Back Up

A WordPress site is two things: the database and the filesystem. Missing either breaks the restore. The database holds posts, options, users, WooCommerce orders and wp_options transients. Use mysqldump (MariaDB 11.4 or MySQL 8.4 in 2026) with --single-transaction --routines --triggers --default-character-set=utf8mb4 so InnoDB tables stay consistent without locking.

The filesystem is everything under the web root plus wp-config.php (which may sit one level above public_html). Include wp-content/uploads, plugins, themes, .htaccess, nginx.conf if used, and any mu-plugins. Exclude nothing that the site needs to boot. If you use object cache or Redis, back up the config file too.

mysqldump -u root -p --single-transaction --routines --triggers 
  --default-character-set=utf8mb4 wordpress_db 
  | gzip > /backup/db/wordpress_db_$(date +%F).sql.gz

tar -czf /backup/files/wordpress_files_$(date +%F).tar.gz 
  -C /var/www/html . 
  --exclude='wp-content/cache/*' 
  --exclude='*.log'

Pitfall: people back up only wp-content and forget wp-config.php or the parent-directory variant. Rollback fix: keep the config inside the tarball by archiving the actual web root and the file path you verified with wp config path. Verify with tar -tzf archive.tar.gz | head to see the file list before you trust it.

Offsite Copies

The offsite copy is the one that survives host failure, ransomware or a bad rm -rf. Push the gzip and tar.gz to remote object storage with lifecycle rules. In 2026 use rclone 1.66+ or AWS CLI v2 against S3, Wasabi or Backblaze B2.

rclone copy /backup remote:wp-backups/site-a 
  --s3-chunk-size=64M --transfers=8 
  --bwlimit=off --log-file=/var/log/rclone-offsite.log

aws s3 cp /backup/db/ s3://my-wp-backups/db/ 
  --recursive --storage-class STANDARD_IA

Encrypt before it leaves the box. Use gpg --symmetric --cipher-algo AES256 or age with a passphrase stored in your secret manager, not in the cron file. Pitfall: syncing to the same provider account that has delete permissions lets an attacker wipe backups too. Use object lock or a separate write-only key. If the transfer fails mid-way, the local copy is your rollback; never delete local until the remote checksum matches with rclone check.

Automation and Retention

Get a Quote

Email + requirement + budget range — we reply with a quote within 24 hours.

Manual backups rot. Put the dump, tar and offsite push in a systemd timer or cron as the site user, not root where avoidable. A daily job plus weekly full and daily incremental is standard. Keep 7 daily, 4 weekly and 3 monthly copies locally, and 30 days of rolling offsite.

# /etc/systemd/system/wp-backup.service
[Unit]
Description=WordPress backup
[Service]
Type=oneshot
User=www-data
ExecStart=/usr/local/bin/wp-backup.sh

# /etc/systemd/system/wp-backup.timer
[Unit]
Description=Daily WP backup
[Timer]
OnCalendar=*-*-* 03:10:00
Persistent=true
[Install]
WantedBy=timers.target

Enable with systemctl enable --now wp-backup.timer and confirm with systemctl list-timers | grep wp. Retention script example using find:

find /backup/db -name '*.sql.gz' -mtime +7 -delete
find /backup/files -name '*.tar.gz' -mtime +7 -delete
rclone delete remote:wp-backups/site-a --min-age 30d

Pitfall: cron emails fail silently when MAILTO is unset. Log to a file and alert on missing timestamps. If a bad backup overwrote good ones, your rollback is the offsite bucket with versioning enabled; restore the previous object version. For teams that prefer managed coverage, our WordPress Maintenance & Care plan runs these jobs with monitored retention so the schedule is not a todo you forget.

Step-by-Step Restore Process

  1. Provision a clean target with the same PHP 8.3+ and MariaDB version, then stop web traffic.
  2. Restore the filesystem tarball into the web root and fix ownership with chown -R www-data:www-data.
  3. Import the database dump and run wp search-replace 'https://old.example' 'https://new.example' --all-tables when the domain changed.
  4. Point DNS or the host vhost at the restored copy and flush caches with wp cache flush.
  5. Run the verification checks below and record the elapsed time in your runbook.

Running the restore commands

Concrete path example on the target box:

tar -xzf /restore/wordpress_files_2026-04-01.tar.gz -C /var/www/html
zcat /restore/wordpress_db_2026-04-01.sql.gz | mysql -u root -p wordpress_db
wp --path=/var/www/html core verify-checksums
wp --path=/var/www/html option get siteurl

Pitfall: forgetting --all-tables on search-replace leaves serialized URLs in postmeta and breaks menus. Rollback: keep the old instance running behind a load balancer or a maintenance IP; if the restore is wrong, flip traffic back and retry with the previous day archive.

Restore Drills

Restore Drills

A backup you never restored is a hypothesis. Schedule a quarterly drill: pick a random archive, restore to a staging host, and time it. Check that wp core verify-checksums passes, that WooCommerce order counts match the source with wp wc order list --fields=id --format=count, and that a page returns HTTP 200 with correct assets.

Compare row counts between live and restored: wp db query 'SELECT COUNT(*) FROM wp_posts' on both sides. Diff the uploads count with find uploads -type f | wc -l. If the restored site shows a white screen, consult fixing a white screen after update for the ten-minute triage path, since a bad plugin in the archive behaves the same as after an update.

Pitfall: drills on the same server as production give false confidence because the database server is already warm. Use a separate VM. If the drill fails, your rollback is the previous archive and the documented gap goes into the next sprint. Track mean time to restore (MTTR) and aim to lower it each quarter.

Teams that need a contractual recovery window often pair this with a WordPress maintenance contract SLA so restore targets are written down and measured rather than assumed.

Frequently asked questions

How often should a WordPress backup run?

Daily for most sites with active content or orders; twice daily for high-volume WooCommerce. Automate with a systemd timer and keep 7 local daily copies plus 30 days offsite.

Is a plugin-only backup enough?

Plugins are fine for convenience but you still need an offsite copy outside the same account and a tested restore. Pair the plugin export with server-level dumps so you control retention and encryption.

What if the restore finishes but the site is broken?

Flip traffic back to the old instance, then check wp core verify-checksums, run search-replace again with --all-tables, and retry with the prior day archive from offsite storage.

Action Checklist

  • Inventory database and filesystem paths and confirm wp config path output.
  • Write the dump + tar + rclone script and run it once by hand.
  • Enable the systemd timer and verify with systemctl list-timers.
  • Set retention with find and remote lifecycle or object lock.
  • Run a timed restore drill on separate hardware and record MTTR.

Need this run for you? See our maintenance services at https://runokwp.com/en/services/maintenance/ and browse more guides in the technical articles index. We set up, monitor and restore.

References & further reading

Need WordPress Development?

Free proposal and quote within 24 hours — we serve clients worldwide.

QQ