Skip to main content

3-2-1 Backup Strategy for WordPress

The 3-2-1 strategy is a simple rule that prevents the most common backup failure: "everything was on the same server". For WordPress on a VPS, 3-2-1 becomes practical when you define exactly what your artifacts are, where each copy lives, and how you verify restores.

Quick Summary
  • 3 copies total: production + at least 2 backups.
  • 2 storage types/failure domains: do not keep all copies on the same disk/provider.
  • 1 offsite: at least one copy outside the VPS.
  • Verify and practice restores or you do not have backups.

What counts as a "copy"

A "copy" is a complete, usable set of data needed to restore.

For WordPress this usually means:

  • Files: WordPress root and wp-content/.
  • Database: MySQL/MariaDB dump.
  • Configuration/secrets: wp-config.php (and .env if used).

One copy can be "live production". The additional copies should be backups that you can restore from.

The numbers explained (without misconceptions)

Three copies

"3" means you have three total copies:

  • Copy 1: production (live site)
  • Copy 2: a backup
  • Copy 3: another backup

It does not mean "three cloud providers".

Example:

three-copies-example.txt
Copy 1: /var/www/html + DB on VPS (production)
Copy 2: /backups on VPS (local restore copy)
Copy 3: remote:wp-backups/site-a (offsite)

Two storage types (or failure domains)

"2" means your backups are not all stored on the same underlying thing.

Good interpretations:

  • VPS disk + object storage
  • VPS disk + another server in a different region
  • attached block volume + cloud storage

Bad interpretations:

  • two folders on the same disk
  • two buckets controlled by the same leaked credential

One offsite copy

"1" means at least one copy is outside the VPS.

Offsite examples:

  • object storage (S3/R2/B2)
  • another VPS (different provider/region)
  • NAS at a different site

A WordPress-specific 3-2-1 map

This is a practical way to map 3-2-1 to WordPress artifacts.

LayerArtifactExample nameNotes
FilesWordPress fileswp-files-2026-03-01.tar.zstexclude caches, nested archives
DBDatabase dumpwp-db-2026-03-01.sql.zstdump frequently; SQL compresses well
Secretsconfig snapshotsecrets-2026-03-01.tar.gpgencrypt before offsite

Example architectures

Small business (simple and good)

architecture-simple.txt
VPS (production)
- /var/www/html + DB
- /backups (local artifacts)

Offsite
- object storage (remote:wp-backups/site-a)

Benefits:

  • fast local restores for minor incidents
  • survivability if the VPS is lost

Agency (strong isolation)

architecture-agency.txt
VPS-A (production)
- local artifacts

VPS-B (backup server, different region)
- receives rsync copies

Object storage
- encrypted long-term retention

Benefits:

  • multiple offsite layers
  • isolation if one provider/account fails

Implementation blueprint (files + DB + offsite)

This blueprint uses:

  • tar + zstd for file archives
  • mysqldump for DB dumps
  • rclone copy for offsite upload

Adjust paths and database name for your environment.

Create local artifacts

create-local-artifacts.sh
set -euo pipefail

umask 077

BACKUP_DIR="/backups"
STAMP="$(date +%F)"

FILES="$BACKUP_DIR/wp-files-$STAMP.tar.zst"
DB="$BACKUP_DIR/wp-db-$STAMP.sql.zst"

mkdir -p "$BACKUP_DIR"

tar -C /var/www/html \
--exclude='wp-content/cache' \
--exclude='wp-content/*/cache' \
--exclude='wp-content/updraft' \
-cf - . \
| zstd -3 -T0 -o "$FILES"

mysqldump --single-transaction --quick --routines --events --triggers \
--column-statistics=0 wordpress \
| zstd -3 -T0 -o "$DB"

zstd -t "$FILES"
zstd -t "$DB"

ls -lh "$FILES" "$DB"

Copy offsite (safe default)

copy-artifacts-offsite.sh
rclone copy /backups remote:wp-backups/site-a \
--include 'wp-files-*.tar.zst' \
--include 'wp-db-*.sql.zst'
warning

Use rclone copy unless you fully understand rclone sync deletions.

Verify offsite

verify-offsite-has-backups.sh
rclone lsf remote:wp-backups/site-a | rg -n 'wp-(files|db)-' | sed -n '1,40p'

Verification and restore drills (the part most people skip)

You can satisfy "3-2-1" on paper and still fail to restore. Add a verification cadence.

Daily checks

  • artifacts exist and are non-empty
  • integrity tests succeed
  • offsite copy exists
daily-backup-sanity-checks.sh
set -e
ls -lh /backups | sed -n '1,60p'
zstd -t /backups/wp-files-*.tar.zst
zstd -t /backups/wp-db-*.sql.zst

Weekly restore drill (staging)

weekly-restore-drill-files.sh
set -e

sudo rm -rf /tmp/restore-test
sudo mkdir -p /tmp/restore-test

sudo tar --use-compress-program=zstd -xf /backups/wp-files-2026-03-01.tar.zst -C /tmp/restore-test
sudo find /tmp/restore-test -maxdepth 3 -type f -name wp-config.php -print
sudo find /tmp/restore-test -maxdepth 3 -type d -name wp-content -print

Database drill (restore into a staging database):

weekly-restore-drill-db.sh
zstd -dc /backups/wp-db-2026-03-01.sql.zst | mysql wordpress_restore

Monthly disaster recovery simulation

Once a month, prove that offsite backups are usable:

  • download (or rsync) one snapshot
  • decrypt if needed
  • restore on a fresh VM/VPS
  • record actual RTO (time to restore)

How 3-2-1 affects RPO and RTO

3-2-1 itself does not set RPO/RTO, but it enables them:

  • Offsite copy reduces the chance of total loss (helps you meet RPO).
  • Local copy reduces restore time (helps you meet RTO).

See:

  • opt/docker-data/apps/docusaurus/site/docs/server/linux-server/10-backup-disaster-recovery/rpo-vs-rto.mdx

Security and isolation notes

If your VPS is compromised, assume attackers can read local backups and may try to delete offsite backups.

Mitigations:

  • encrypt sensitive artifacts before upload
  • separate credentials for backup upload
  • enable remote versioning/immutability if possible
  • keep decryption material out of the VPS when feasible

Quick self-audit

Answer these and you will know whether you really have 3-2-1:

  • Do you have at least two backup copies in addition to production?
  • Are those copies in different failure domains?
  • Do you have at least one offsite copy?
  • Can you restore both files and database into staging?
  • Are backups protected from web access and casual reads?

Practice lab

Use this lab to implement a minimal 3-2-1 setup.

  1. Create /backups and lock it down.
lab-create-backup-dir.sh
sudo mkdir -p /backups
sudo chmod 700 /backups
  1. Create one files archive and one DB dump.
lab-create-one-snapshot.sh
sudo tar -C /var/www/html -cf - . | zstd -3 -T0 -o "/backups/wp-files-$(date +%F).tar.zst"
mysqldump --single-transaction --quick wordpress | zstd -3 -T0 -o "/backups/wp-db-$(date +%F).sql.zst"
  1. Verify both.
lab-verify-artifacts.sh
zstd -t /backups/wp-files-*.tar.zst
zstd -t /backups/wp-db-*.sql.zst
  1. Copy offsite.
lab-copy-offsite.sh
rclone copy /backups remote:wp-backups/site-a
  1. Restore into staging.
lab-restore-into-staging.sh
sudo rm -rf /tmp/restore-test
sudo mkdir -p /tmp/restore-test
sudo tar --use-compress-program=zstd -xf /backups/wp-files-*.tar.zst -C /tmp/restore-test

If you can do this reliably, you have a real foundation to build on.


Common 3-2-1 variants (optional)

3-2-1 is the baseline. You may also see variants that add specific guarantees.

3-2-1-1

Adds an extra "1" meaning one copy is:

  • offline (not continuously reachable from the VPS), or
  • immutable (cannot be modified/deleted for a retention window).

This helps against ransomware and credential compromise.

3-2-1-1-0

Adds "0" meaning "zero errors" verified:

  • integrity tests pass (compression/encryption)
  • restore drills succeed
  • checksums match for offsite copies

What counts as "two storage types"

This is about failure domains, not file extensions.

Examples:

Storage type AStorage type BWhy it counts
VPS local diskobject storage bucketdifferent systems, different durability model
attached block volumeanother VPS in a different regionreduces chance of same physical failure
NAS (different site)object storagephysical + administrative separation

Non-examples:

Storage type AStorage type BWhy it does not help
/backups folder/home/backups folderstill the same disk/system
two cloud folderssame compromised credentialdeletion risk remains

Isolation checklist (protect offsite backups)

Use this checklist to reduce "attacker deletes backups" risk:

  • Backups are written to a directory not served by the web server.
  • Remote credentials are not readable by www-data.
  • Offsite storage has versioning/immutability (if available).
  • Encryption keys/passphrases are not stored in the same place as the encrypted backups.
  • You can revoke/rotate remote credentials without losing access to existing backups.

Bandwidth-friendly 3-2-1

If uploads are large, offsite transfer time is often the limiting factor.

Practical tactics:

  • Keep frequent DB dumps (small, compressible).
  • Use snapshot-style file backups to avoid re-uploading unchanged files.
  • Run offsite uploads off-peak.
  • Apply rate limiting if needed.

Example: offsite copy with a bandwidth limit:

rclone-copy-with-bandwidth-limit.sh
rclone copy /backups remote:wp-backups/site-a --bwlimit 8M

A documentation template you can keep with the client

Store a short document describing your actual backup map.

backup-map-template.txt
Site name:
Primary host:
WordPress root:
Database name:

Artifacts:
- files archive pattern:
- db dump pattern:
- secrets archive pattern:

Local storage:
- path:
- retention:

Offsite storage:
- provider/remote name:
- path:
- retention:

Encryption:
- method:
- key/passphrase location:

Verification:
- daily checks:
- weekly restore drill:
- monthly DR simulation:

FAQ

Do I need three different clouds?

No. You need three total copies with at least one offsite. Multiple clouds are optional.

Is a provider snapshot the same as an offsite copy?

Sometimes. Many provider snapshots live in the same provider account and may not protect against account-level deletion. Treat them as helpful, but not your only offsite.

Do I need to back up WordPress core?

Often you can reinstall it, but full archives are still useful for speed and for unknown drift. If you back up only wp-content/, do it intentionally and practice restores.

How often should I do restore drills?

At least monthly. If the site is revenue-critical, consider weekly drills.

Troubleshooting

Offsite copy exists but restore fails

Common causes:

  • wrong paths stored in the archive
  • missing secrets (wp-config.php)
  • database dump does not match the restored file set

Fix by restoring into staging, validating layout, and documenting a known-good restore procedure.

Offsite is missing some backups

Confirm whether you are using copy vs sync, and whether retention pruning is happening remotely.

rclone-audit-offsite-contents.sh
rclone lsf remote:wp-backups/site-a --recursive | sed -n '1,120p'

Local backups keep filling the disk

This is a retention issue. Confirm pruning rules and ensure backups are not nested.

audit-local-backup-growth.sh
du -sh /backups
ls -lh /backups | sed -n '1,120p'