All WordPress HTML Templates Forms & Webhooks AI & Tools
WordPress

WordPress Backups: The 3-2-1 Rule Applied to Real Sites

A practitioner’s guide to the 3-2-1 rule for WordPress backup: split DB from files, use immutable object storage, and restore WordPress fast with WP-CLI.

A real working moment illustrating the theme of an article about wordpress backup. Wide 16:9 banner, one strong focal point, magazine editorial quality, authentic and unstaged.

A client came to us with a WooCommerce site that had been silently broken for nine days. A plugin update in the small hours had corrupted the wp_postmeta table, and their backup plugin had dutifully captured the corruption every night since. Thirty daily backups. All of them useless.

That’s the failure mode nobody writes about. Most WordPress backup advice stops at “install a plugin and pick a schedule”, which solves the easy half of the problem. The hard half is retention depth, where the copies physically live, whether anyone can actually restore from them, and how long that restore takes while the client is on the phone.

The 3-2-1 rule is old, boring sysadmin doctrine, and it still works. Three copies of your data, on two different media or platforms, one of them offsite. Here’s what that looks like when the data is a WordPress install with a 40GB uploads folder and a database full of Woo orders.

Key Takeaways

  • Host snapshots are copy one, never copy three. They live in the same account as the site, so a compromised or closed hosting account takes the backups with it.
  • Back up the database and the files on different schedules. The database changes hourly on an active site, wp-content/uploads barely changes at all, and treating them as one blob is why big-site backups time out.
  • Retention depth matters more than frequency. Seven dailies will not save you from corruption or a hack discovered three weeks later. Aim for 14 dailies, 8 weeklies, 6 monthlies as a floor.
  • Use object storage with versioning and an object-lock or immutability policy for the offsite copy. If the credentials on your server can delete the backups, ransomware can too.
  • An untested backup is a hypothesis. Schedule a quarterly restore drill onto a staging site and time it, so your recovery time estimate is a measurement rather than a guess.

What 3-2-1 means for a WordPress backup

The canonical version: keep three copies of your data, on two distinct storage types, with one copy offsite. The production site counts as copy one. So you need two more backups, and at least one of them has to live somewhere that a compromise of your hosting account cannot reach.

Mapped onto a real client site, that usually comes out as:

  • Copy 1: the live site plus the host’s automated daily snapshot. Fast to restore, same blast radius as production.
  • Copy 2: a nightly push from the server to object storage (Backblaze B2, Cloudflare R2, Wasabi, S3). Different vendor, different credentials.
  • Copy 3: a weekly pull from a machine you control, or a cross-region replicated bucket with versioning and object lock enabled. Nothing on the web server can delete it.

The “two different media” part gets people arguing. In 2026, nobody is writing to LTO tape for a brochure site. Two different platforms with two different sets of credentials is the practical modern reading. It defends against the same underlying threat: a single point of failure that takes out the original and the backup at the same time.

Split the database from the files

This is the single change that fixes most broken backup setups. The database and the filesystem have completely different change rates, sizes and restore requirements. Bundling them into one nightly ZIP is wasteful at best and fatal at worst.

The database on a busy site changes constantly: orders, comments, sessions, transients, WooCommerce lookup tables. It’s also small, usually 50MB to 2GB compressed. Back it up hourly if the site takes money, every four hours if it doesn’t.

wp-content/uploads, meanwhile, is enormous and almost immutable. Files get added; they rarely change. A full tarball of it every night is pointless I/O. Use incremental or sync-based backups so you only ship the delta. If you have been through the image optimisation work and generated six sizes plus WebP and AVIF for every upload, your uploads directory has probably tripled since launch. Incremental matters even more then.

Themes and plugins are copy three of themselves already: they’re in version control or on wordpress.org. Back them up anyway, because plugin versions get pulled and paid plugins disappear, but they don’t need daily treatment.

#!/usr/bin/env bash
set -euo pipefail

SITE=/var/www/example.com
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
OUT=/var/backups/wp

cd "$SITE"

--single-transaction keeps InnoDB consistent without locking the site

wp db export "$OUT/db-$STAMP.sql" \ --single-transaction \ --quick \ --default-character-set=utf8mb4 \ --exclude-tables=wpactionschedulerlogs gzip -9 "$OUT/db-$STAMP.sql"

verify the archive is readable before we ship it anywhere

gzip -t "$OUT/db-$STAMP.sql.gz"

wp-config.php lives outside the repo and holds the salts: back it up separately

cp wp-config.php "$OUT/wp-config-$STAMP.php"

Two things there earn their keep. --single-transaction gives you a consistent InnoDB dump without a global read lock, which is the difference between a clean backup and a 20 second outage at 3am. Excluding the Action Scheduler log tables can cut a Woo database dump by half or more, with zero loss of anything you’d want back.

Host snapshots are useful and they are not a strategy

Every managed WordPress host advertises automated backups. Kinsta, WP Engine, SiteGround, Cloudways: they all take daily snapshots and most keep 14 to 30 days. Use them. They restore in one click and they’ll cover 80% of your incidents, mostly the ones where a plugin update breaks the checkout.

They are still copy one. Three specific scenarios kill them:

  1. Account compromise. An attacker with your host panel login can delete the snapshots along with the site.
  2. Billing failure. Card expires, client ignores three emails, account suspended, data purged. We have seen this exact sequence twice.
  3. You leave the host. Snapshot formats are proprietary. Try restoring a WP Engine backup point onto a bare VPS at 2am and see how that goes.

The rule we apply on every retainer: if the only copy of the site is inside the hosting account, there is no backup.

Copy two: push to object storage nightly

For copy two we use restic against a B2 or R2 bucket. It’s deduplicating, encrypted client-side, and its incremental backups of a large uploads directory finish in seconds once the first run is done. Alternatives that work fine: BorgBackup with a Borg-capable host, or plain rclone sync if you don’t need deduplication.

export RESTIC_REPOSITORY="b2:acme-wp-backups:example.com"
export RESTICPASSWORDFILE=/root/.restic-pass
export B2ACCOUNTID="..." B2ACCOUNTKEY="..."

restic backup \
  /var/www/example.com/wp-content/uploads \
  /var/www/example.com/wp-content/plugins \
  /var/www/example.com/wp-content/themes \
  /var/backups/wp \
  --exclude="/cache/" \
  --exclude="/wp-content/uploads/wc-logs/" \
  --tag nightly

Retention: keep depth, not just recency

restic forget \ --keep-daily 14 \ --keep-weekly 8 \ --keep-monthly 6 \ --prune

Monthly integrity check reading 5% of actual pack data

restic check --read-data-subset=5%

The B2 application key for this server should be scoped to write and list only, with no delete permission. Run the forget --prune step from a different machine with a separate key. It’s ten minutes of extra setup and it means a fully compromised web server cannot wipe your history.

The plugin route is legitimate too, if the client will be managing this themselves. BlogVault and UpdraftPlus Premium both do incremental file backups to storage you own. BlogVault in particular handles the restore from its own infrastructure rather than the crashed server, which matters when the site is down because the host is down. What we avoid is any plugin that stores backups in wp-content/ and nowhere else. That’s not a second copy. That’s a bigger tarball on the same disk.

Copy three: offsite, versioned, and out of reach

Copy three exists for one reason: the scenario where copies one and two are both compromised or both wrong. Ransomware that sits dormant for a fortnight. Corruption you didn’t notice. An automated pruning job that ate your history because someone fat-fingered a retention flag.

Enable object versioning and an object lock (S3 Object Lock, B2 File Lock) with a compliance period of 30 days on the destination bucket. Locked objects cannot be deleted by anyone, including you, including the root account key, until the period expires. That is the whole point.

If cloud-only makes you nervous, the low-tech version works: a weekly rclone sync from the office NAS or a studio machine, pulling from the bucket rather than pushing from the server. Pull-based backups are structurally safer because the credentials live somewhere the attacker isn’t.

One more thing to store with copy three: the recovery information. DNS registrar login, host account, database credentials, the encryption passphrase for the restic repo. A perfect encrypted backup with a lost passphrase is a very expensive collection of random bytes.

How to restore WordPress when it actually matters

Restores go wrong in predictable places. Here’s the sequence we follow, in order, because doing it out of order costs you an hour.

# 1. Put the site in maintenance mode BEFORE touching the database
wp maintenance-mode activate

2. Restore files first

restic restore latest --target /var/www/example.com --include /wp-content

3. Reset the database, then import. Skipping the reset leaves orphan tables

from plugins that were installed after the backup was taken.

wp db reset --yes gunzip -c /var/backups/wp/db-20260114T030000Z.sql.gz | wp db import -

4. If restoring to staging, fix serialised URLs properly

wp search-replace 'https://example.com' 'https://staging.example.com' \ --all-tables-with-prefix --precise --skip-columns=guid

5. Flush everything that caches a stale state

wp cache flush wp rewrite flush --hard wp transient delete --all wp maintenance-mode deactivate

Three details worth internalising. wp db reset before import prevents the orphan-table problem, where a plugin from after the backup date leaves tables behind and the site half-works in a confusing way. --skip-columns=guid is non-negotiable: rewriting GUIDs breaks feed readers and can duplicate items in aggregators. And --precise forces PHP-based serialisation handling instead of the faster SQL path. You want that any time widgets, ACF field groups or theme mods are involved, since those are serialised arrays that a naive string replace will corrupt.

Run the drill

Once a quarter, restore the most recent backup to a throwaway staging environment and time it end to end. Write the number down. For a mid-size Woo site with a 12GB uploads folder and a 900MB database, ours lands around 25 to 40 minutes, most of which is file transfer. That number is your recovery time objective. Clients understand “we can be back in about 40 minutes” far better than “we have backups”.

The drill also catches the silent failures: the cron job that stopped running in November, the bucket that filled up, the database dump that’s been 0 bytes for three weeks because the MySQL password rotated.

A schedule that survives contact with real clients

Frequency is a function of how much work the client is willing to lose. Ask it that way, not as “how often should we back up”.

  • Brochure or portfolio site: daily database, weekly files, 30 day retention. Content changes monthly. Nobody loses money if you roll back a day.
  • Publishing or content site: database every 6 hours, files nightly, 14 dailies plus 8 weeklies plus 6 monthlies. Editors will not re-type an article.
  • WooCommerce or membership: database hourly with binary log or point-in-time recovery if the host supports it, files nightly, same retention depth. A lost hour of orders is a support nightmare, not a technical one.

Cost is rarely the blocker people expect. B2 sits around 6 USD per TB per month, and a deduplicated restic repo for a typical site with six months of history often lands under 30GB total. That’s pennies. The real cost is the hour of setup and the discipline of the quarterly drill.

What you can rebuild instead of restoring

Not everything needs backing up with equal care. If your theme is in Git, your reusable layouts live in a versioned block pattern library, and your field definitions are registered in PHP rather than clicked into the database, then a catastrophic loss means restoring content and media, not rebuilding a site. Sites built on a well-structured starting point like CanvasWP or a custom theme in version control recover faster because less of the site’s definition lives in wp_options. Every configuration decision you move from the database into code is one less thing your backup has to be perfect about.

Just make sure the exception list is deliberate. Excluding wp-content/cache is smart. Excluding wp-content/uploads because it’s big is how people discover they have no images.

Frequently Asked Questions

Is my host’s daily backup enough on its own?

No, because it lives inside the same account as the site. It covers plugin update accidents and bad edits perfectly well, which is most incidents, but it does nothing for account compromise, billing suspension or host outage. Treat it as your fast-restore layer and add at least one copy in storage you control separately.

How long should I keep WordPress backups?

Depth beats frequency. Seven days is not enough because compromises and data corruption are often discovered weeks later, after every backup in the window already contains the problem. A sensible floor is 14 daily, 8 weekly and 6 monthly restore points, which deduplication makes cheap to store.

Do I need to back up WordPress core files?

Not really, since core is reproducible with wp core download --version=6.x in about 20 seconds. What you must back up is wp-config.php (it holds your salts and DB credentials), the wp-content directory, and the database. Backing up core anyway costs almost nothing with deduplication, so there’s no strong reason to exclude it either.

Why does my backup plugin time out on large sites?

Because it’s trying to build one archive of everything inside a PHP request with a hard execution limit, and a 30GB uploads directory will never finish. Move to incremental file backups that only transfer changed files, or run the backup from the shell via WP-CLI and cron where PHP timeouts don’t apply. Splitting the database job from the file job usually fixes it on its own.

What is the safest way to restore WordPress to a staging site?

Restore the files and database first, then run wp search-replace with --precise and --skip-columns=guid to rewrite URLs without corrupting serialised data or breaking feeds. Immediately after, disable any live payment gateways, transactional email and SEO indexing on the staging copy. Forgetting that last step is how a staging restore emails 4,000 customers.

Pick one action from this: open your backup destination right now and check the timestamp on the most recent file. Not the plugin’s dashboard, which reports what it intended to do. The actual bucket, the actual file, the actual size. We have found more dead backup jobs that way than through any monitoring alert.

If the timestamp is current, book a restore drill for next quarter and time it. If it isn’t, you just avoided the worst phone call in this job.

immutable offsite wordpress backups object lock incremental wordpress backups restic b2 restore wordpress from backup wp-cli wordpress backup wordpress backup 3-2-1 rule wordpress backup retention schedule wordpress backup strategy for woocommerce wp-cli db export single-transaction