Backups and Disaster Recovery
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages. |
WordPress core has no backup feature. A site is two things stored in two places, a MySQL or MariaDB database and a directory of files, and a usable backup captures both, at the same moment, somewhere the failure that destroys the site cannot reach. A backup is only proven by a restore, and the time that restore takes is the real recovery time. This page is for administrators and site owners: it defines what to copy, gives a complete backup script and a complete restore script for the Lumen Press site, measures the restore, and covers off-site storage, backup plugins, host backups and failover.
This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the
version to run in production), PHP 7.4 minimum and 8.3+ recommended, WP-CLI 2.12.0 and MariaDB 11.8, and checked on
2026-10-28. Both scripts were run against a Lumen Press test site (3,000
lp_book posts, 500 posts, 60 cover images with their generated sizes, about 82 MB of uploads) and restored into a new,
empty database server and an empty web root.
What a WordPress backup must contain
The official backup guide splits the job into the database (content, users, settings) and the files (core, themes,
plugins, uploads, wp-config.php, .htaccess). Neither half is useful without the other: uploads without the database
have no posts pointing at them, and a database without wp-content references themes, plugins and images that are gone.
| Part | Where | Changes | Backup approach |
|---|---|---|---|
Database |
The database named by |
Every edit, comment, login, cron run and order |
|
Uploads |
|
Every media upload and every regenerated image size |
Archive or incremental sync ( |
Plugins, themes, must-use plugins, languages |
|
On installs and updates |
Archive with |
Drop-ins |
|
Rarely |
Archive with |
|
The WordPress root, or one level above it |
Rarely |
Archive; it holds database credentials and the authentication keys and salts, so the archive is a secret |
|
The WordPress root (Apache only) |
On permalink changes and security rules |
Archive; nginx sites keep their server block in configuration management instead |
Core files |
Everything else under the root ( |
Only on core updates |
Not needed: download the same version again; record the version with the backup |
Some paths under wp-content are regenerable or temporary and only make backups larger and slower:
-
wp-content/cache/, written by page-cache plugins and rebuilt on the next requests. -
wp-content/upgrade/, the scratch directory for updates, andwp-content/upgrade-temp-backup/, where WordPress 6.3+ moves the previous version of a plugin or theme during an update so it can roll back a failed update. It is emptied after the update; it is not a backup and must not be treated as one. -
wp-content/debug.log, whenWP_DEBUG_LOGis on (it may also contain data that should not be kept). -
Archives written by backup plugins into
wp-contentorwp-content/uploads(for examplewp-content/updraft/orwp-content/ai1wm-backups/). Archiving them backs up the backups, and storing them under the web root can make them downloadable.
A complete disaster recovery also needs what is outside the WordPress directory: the web server and PHP configuration, TLS certificates (or the means to reissue them), system cron entries such as the one that replaces WP-Cron, DNS records and the credentials for the off-site storage. Keeping those in configuration management or infrastructure-as-code is cheaper than backing them up by hand. A WXR export from Tools > Export is not a backup either: it omits settings, password hashes, themes, plugins and media files; see Importing, Exporting and Migrating.
RPO, RTO and a backup plan
Two numbers turn "regular backups" into a plan:
-
Recovery point objective (RPO): the most data, measured in time, that the site can afford to lose. A nightly backup means an RPO of up to 24 hours: an editor’s work from the afternoon and every comment since the last run are gone.
-
Recovery time objective (RTO): how long the site may be down before it is serving again. It includes noticing the failure, getting a server, fetching the backup, restoring and checking, not just the import.
The official guide suggests weekly backups for small, quiet sites, daily backups for busy ones, a backup before every upgrade or move, and keeping at least three to five recent backups in several places. Frequency should follow the RPO and the part that changes: for Lumen Press the database changes all day and is small, the uploads change a few times a day and are large, and code changes only through deployments.
| Lumen Press data | Target RPO | Copy | Retention |
|---|---|---|---|
Database |
1 hour |
Hourly |
48 hourly copies, 30 daily, 12 monthly |
Uploads and |
24 hours |
Nightly archive (or a continuous sync to object storage) |
30 daily, 12 monthly |
Custom code ( |
0 |
Git repository and release artefacts |
Repository history |
Server configuration |
On change |
Infrastructure-as-code repository |
Repository history |
The 3-2-1 rule is a useful minimum: three copies of the data, on two different kinds of storage, one of them off-site (another provider or region, with separate credentials). The target RTO for Lumen Press is one hour; the restore test later on this page shows how much of that the restore itself consumes.
Backing up the database
wp db export
wp db export runs mysqldump or mariadb-dump, whichever the server provides, with the credentials from
wp-config.php, so they never appear on the command line or in the process list. Any option it does not know is passed
to the dump tool, which is how --single-transaction reaches it. Writing to - sends the dump to standard output for
compression in the same pipe. Without a file name it writes {dbname}-{Y-m-d}-{random-hash}.sql in the current
directory, which is often the web root.
# Consistent snapshot of every table, compressed while it is written
wp db export - --single-transaction --quick --default-character-set=utf8mb4 \
| gzip > /var/backups/lumen/lumen-db-$(date -u +%Y%m%dT%H%M%SZ).sql.gz
# Only the WordPress tables, when the database is shared with another application
wp db export lumen-wp-only.sql --single-transaction \
--tables="$(wp db tables --all-tables-with-prefix --format=csv)"
Source: WP-CLI: wp db export, WP-CLI: wp db tables
By default wp db export dumps every table in the database, not only those with the WordPress prefix. On the test
server, a database holding the 12 WordPress tables and one table of another application produced a dump with 13
CREATE TABLE statements; with --tables="$(wp db tables --all-tables-with-prefix --format=csv)" it produced 12. It
also adds --no-tablespaces unless --include-tablespaces is given, so the database user does not need the PROCESS
privilege. wp db import is the reverse: it reads a file or standard input (-), turns off unique and foreign-key
checks during the import (--skip-optimization keeps them) and adapts the session SQL mode for WordPress.
mariadb-dump and mysqldump directly
On hosts without WP-CLI, or for a backup user with read-only privileges, call the dump tool directly. Keep the
credentials in an option file readable only by the backup user rather than in -p<password> on the command line.
# /etc/lumen-backup.cnf (chmod 600, owned by the backup user)
[client]
host=db.lumenpress.internal
user=lumen_backup
password=<backup-user-password>
Source: MariaDB: mariadb-dump
mariadb-dump --defaults-extra-file=/etc/lumen-backup.cnf \
--single-transaction --quick --routines --default-character-set=utf8mb4 \
lumen | gzip > /var/backups/lumen/lumen-db.sql.gz
The options matter:
-
--single-transactionstarts a transaction before reading, so InnoDB tables (the engine WordPress creates by default) are dumped as one consistent snapshot while the site keeps writing. It does not protect MyISAM or MEMORY tables, which some old plugins still create, and other connections must not runALTER TABLE,CREATE TABLE,DROP TABLE,RENAME TABLEorTRUNCATE TABLEduring the dump; a plugin update that alters its tables while the backup runs can make the dump fail or read wrong data. Schedule backups away from deployments. Without the option the tool locks each table as it reads it, so a post and its metadata can come from different moments. -
--quickstreams rows instead of buffering whole tables in memory (it is on by default inmariadb-dump). -
--routinesadds stored procedures and functions, which WordPress does not use but some plugins do. Triggers are dumped with their tables by default. -
--default-character-set=utf8mb4matches the character set WordPress uses since 4.2;mariadb-dumpdefaults to it.
The official database page shows mysqldump --add-drop-table without --single-transaction; adding it is what makes
a dump of a live site consistent. Since MariaDB 11.0 the mysqldump name is a deprecated symlink to mariadb-dump,
and the MariaDB Docker image no longer ships it. A mariadb-dump from current MariaDB versions also starts with
a /*M!999999\- enable the sandbox mode */ line. The MariaDB documentation says older mariadb clients and the
MySQL mysql client cannot interpret it; the MySQL 8.4 client imported such a dump without error on the test
server, but when restoring into MySQL with an older client, remove the first line (sed 1d) or import with
wp db import, which passes the same file to the client in the target environment.
phpMyAdmin
Shared hosts often offer only phpMyAdmin (through cPanel or Plesk). Its Export tab has a Quick mode (all tables, SQL
format) and a Custom mode, where the official guide recommends selecting the WordPress tables, gzip or zip output, SQL
format and "Add DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER statement". Restoring is the Import tab of
the target database. phpMyAdmin runs inside PHP, so large databases hit max_execution_time, memory_limit and the
upload_max_filesize limit on import; the official guide warns that it cannot handle large databases and points to
the command line or MySQL Workbench. It cannot be scheduled, so it is a tool for an occasional manual copy, not a
backup strategy. Older books present it as the main way to back up the database; on any host with SSH, WP-CLI or the
dump tool is faster and repeatable.
Larger databases and point-in-time recovery
A logical dump is simple and portable but slow to restore for multi-gigabyte databases, because the import rebuilds
every index. Beyond a few gigabytes, physical backups (mariadb-backup for MariaDB, the managed snapshots of a cloud
database service) restore much faster. For an RPO of minutes rather than hours, keep the server’s binary log and
replay it on top of the last full backup up to the moment before the failure (point-in-time recovery). The
transaction model they rely on is described in Transaction Control.
On Amazon RDS or Aurora, the automated backups, point-in-time recovery window, final snapshot and deletion protection are instance settings; see Managed Database: RDS and Aurora. In a cluster, the uploads live in a versioned bucket and the code in an image registry, so a backup plan covers those too; see Media Offload: S3, EFS and CDN.
Backing up the files
The files part is wp-content, wp-config.php and .htaccess. Core is better downloaded again at the recorded
version, which also guarantees that the restored core is unmodified. A compressed tar archive keeps owners,
permissions and timestamps and can exclude the regenerable paths listed earlier:
tar -C /var/www/html -czf /var/backups/lumen/files.tar.gz \
--exclude='wp-content/cache' \
--exclude='wp-content/upgrade' \
--exclude='wp-content/upgrade-temp-backup' \
--exclude='wp-content/debug.log' \
wp-content wp-config.php .htaccess
On the test site the uploads directory held 82 MB of JPEG files plus a 29 MB page cache. The archive was 17.5 MB and
contained 866 entries, none of them under wp-content/cache/ and no debug.log. Already-compressed media (JPEG, WebP,
AVIF, MP4) barely shrink, so for sites with tens of gigabytes of uploads an incremental copy (rsync to a backup
host, or an object-storage sync with versioning) is far cheaper than a nightly full archive, and the archive then
covers only code, configuration and drop-ins. FTP/SFTP downloads and sync tools such as WinSCP, which the
official guide also lists, work for small sites but are manual.
A complete backup script
The script below is what the Lumen Press server runs every night. It exports the database first (as the official guide
recommends), archives the files, records what is needed to rebuild the site, writes and immediately verifies SHA-256
checksums, tests the gzip stream, and deletes local backup directories older than the retention period. Archives are
created with umask 077 because they contain wp-config.php and personal data. It needs WP-CLI, GNU tar, gzip,
sha256sum and find, and runs as the user that owns the site files.
#!/usr/bin/env bash
# lumen-backup.sh: full backup of the Lumen Press site (database + files).
set -euo pipefail
SITE_PATH="${SITE_PATH:-/var/www/html}" # WordPress root (where wp-config.php lives)
BACKUP_ROOT="${BACKUP_ROOT:-/var/backups/lumen}" # local staging area; copy off-site afterwards
RETAIN_DAYS="${RETAIN_DAYS:-14}" # local retention, in days
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
DEST="$BACKUP_ROOT/$STAMP"
umask 077 # archives contain secrets: owner-only
mkdir -p "$DEST"
# 1. Database first: a consistent InnoDB snapshot, streamed and compressed.
wp --path="$SITE_PATH" db export - --single-transaction --quick \
--default-character-set=utf8mb4 | gzip -6 > "$DEST/db.sql.gz"
# 2. Files: wp-content (minus regenerable data) plus the root files that are not core.
tar -C "$SITE_PATH" -czf "$DEST/files.tar.gz" \
--exclude='wp-content/cache' \
--exclude='wp-content/upgrade' \
--exclude='wp-content/upgrade-temp-backup' \
--exclude='wp-content/debug.log' \
--exclude='wp-content/uploads/*/backups' \
wp-content wp-config.php $( [ -f "$SITE_PATH/.htaccess" ] && echo .htaccess )
# 3. Facts needed to rebuild: core version, locale, home URL, active plugins.
{
echo "core_version=$(wp --path="$SITE_PATH" core version)"
echo "locale=$(wp --path="$SITE_PATH" option get WPLANG 2>/dev/null || true)"
echo "home=$(wp --path="$SITE_PATH" option get home)"
echo "table_prefix=$(wp --path="$SITE_PATH" config get table_prefix)"
echo "plugins=$(wp --path="$SITE_PATH" plugin list --status=active --field=name | paste -sd, -)"
} > "$DEST/manifest.txt"
# 4. Checksums, verified immediately (catches a truncated write).
( cd "$DEST" && sha256sum db.sql.gz files.tar.gz manifest.txt > SHA256SUMS \
&& sha256sum -c SHA256SUMS >/dev/null )
gzip -t "$DEST/db.sql.gz"
# 5. Retention: delete local backup directories older than RETAIN_DAYS.
find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -type d -name '20*Z' \
-mtime +"$RETAIN_DAYS" -exec rm -rf {} +
echo "Backup written to $DEST"
du -sh "$DEST"/*
Source: Advanced Administration: Backing Up Your WordPress Site, WP-CLI: wp db export, WP-CLI: wp core version
A run on the test site took about 5 to 11 seconds and printed:
Backup written to /var/backups/lumen/20261028T104047Z
4.0K /var/backups/lumen/20261028T104047Z/SHA256SUMS
84.0K /var/backups/lumen/20261028T104047Z/db.sql.gz
17.5M /var/backups/lumen/20261028T104047Z/files.tar.gz
4.0K /var/backups/lumen/20261028T104047Z/manifest.txt
set -o pipefail is essential: without it a failed wp db export piped into gzip still exits 0 and leaves a valid,
empty-looking db.sql.gz. The retention step was checked by adding a directory dated 2026-10-01; the next run with
RETAIN_DAYS=14 removed it and kept the two runs from 2026-10-28. Retention on the local disk only bounds disk usage:
the long-term copies (monthly, yearly) belong in the off-site store, with its own lifecycle rules.
Schedule the script with the system cron, not with WP-Cron. WP-Cron only runs when someone visits the site, so on a quiet night a WP-Cron backup can be hours late, and it runs inside a web request with PHP time limits (see WP-Cron and Scheduled Tasks).
# /etc/cron.d/lumen-backup
# Nightly full backup at 02:15 UTC, hourly database-only copy.
15 2 * * * www-data SITE_PATH=/var/www/lumen /usr/local/bin/lumen-backup.sh >> /var/log/lumen-backup.log 2>&1
5 * * * * www-data wp --path=/var/www/lumen db export - --single-transaction | gzip > /var/backups/lumen/hourly-$(date -u +\%H).sql.gz
The % signs are escaped because cron treats an unescaped % as a newline. Naming the hourly files by hour
(hourly-00 to hourly-23) gives a rolling 24-hour window without a separate clean-up. Monitor both jobs: alert when
the newest backup is older than the RPO or when its size drops sharply, which is how a silently failing backup is
usually noticed.
Off-site copies and protecting backups
A backup on the same server, disk or account as the site does not survive the failures that matter most: a lost server, a deleted hosting account, ransomware or an attacker with the site’s credentials. After each run, copy the backup directory to storage in another provider or region, with credentials that the web server cannot use to delete it.
# Appended to lumen-backup.sh: copy the new backup to an off-site remote configured in rclone
rclone copy "$DEST" "offsite:lumen-backups/$STAMP" --immutable
Source: rclone copy
--immutable makes rclone refuse to modify files that already exist on the remote. On the storage side, enable object
versioning or an object lock (write once, read many) with a retention period, so that a compromised server can add
backups but not erase them. Further protections:
-
Encrypt backups that leave the server (an rclone
cryptremote,age,gpg), because they contain the authentication salts, database credentials, user emails and password hashes. Keep the decryption key somewhere the disaster cannot reach, such as a password manager, not only on the server. -
Never store backups under the web root. An archive in
wp-content/uploads/or the document root can be found and downloaded by anyone; deny access to such directories in the web server if a plugin insists on them. -
Restrict permissions to the backup user (
umask 077in the script;0600files,0700directories). -
Treat the backup as personal data under privacy law: retention periods and erasure requests apply to it too.
Restoring
The official order is: restore the files, then import the database, and update wp-config.php if the database
credentials changed. The restore script below does exactly that into an empty web root and an empty database: it
refuses a backup whose checksums do not match, downloads the core version recorded in the manifest (the package without
default themes and plugins), unpacks wp-content, wp-config.php and .htaccess, points wp-config.php at the
replacement database server when NEW_DB_* variables are set, imports the database and runs the post-restore checks.
#!/usr/bin/env bash
# lumen-restore.sh: rebuild the Lumen Press site from one backup directory.
set -euo pipefail
BACKUP_DIR="$1" # e.g. /var/backups/lumen/20261028T020000Z
SITE_PATH="${SITE_PATH:-/var/www/html}"
# 1. Refuse to restore a damaged backup.
( cd "$BACKUP_DIR" && sha256sum -c SHA256SUMS )
CORE_VERSION="$(sed -n 's/^core_version=//p' "$BACKUP_DIR/manifest.txt")"
# 2. Core from WordPress.org, at the exact version that wrote the database.
mkdir -p "$SITE_PATH"
php -d memory_limit=512M "$(command -v wp)" core download \
"https://wordpress.org/wordpress-${CORE_VERSION}-no-content.zip" --path="$SITE_PATH"
# 3. wp-content, wp-config.php and .htaccess from the archive.
tar -C "$SITE_PATH" -xzf "$BACKUP_DIR/files.tar.gz"
# 3b. The replacement database server may differ from the one in the archive.
for c in DB_HOST DB_NAME DB_USER DB_PASSWORD; do
v="NEW_$c"
if [ -n "${!v:-}" ]; then wp --path="$SITE_PATH" config set "$c" "${!v}" --quiet; fi
done
# 4. Database: wp-config.php now points at the (new, empty) database.
wp --path="$SITE_PATH" db create 2>/dev/null || true
gunzip -c "$BACKUP_DIR/db.sql.gz" | wp --path="$SITE_PATH" db import -
# 5. Checks and clean-up.
wp --path="$SITE_PATH" core verify-checksums
wp --path="$SITE_PATH" core update-db
wp --path="$SITE_PATH" cache flush
wp --path="$SITE_PATH" rewrite flush
wp --path="$SITE_PATH" option get home
Source: Advanced Administration: Backing Up Your WordPress Site, WP-CLI: wp core download, WP-CLI: wp db import, WP-CLI: wp core verify-checksums
The core package is downloaded by URL because --skip-content cannot be combined with a URL download (WP-CLI 2.12.0
answers "Skip content and locale options are not available for URL downloads"); the -no-content package is the one
WordPress.org builds for that purpose. Using the exact version matters: a newer core would run database upgrades on the
restored data, and an older one would refuse to work with a database written by a newer version. wp core update-db
is a no-op when the versions match, as the output below shows, and finishes the job after a deliberate core upgrade.
The site was restored with the database password changed on the new server:
NEW_DB_HOST=db2.lumenpress.internal NEW_DB_PASSWORD="$LUMEN_DB_PASSWORD" \
time ./lumen-restore.sh /var/backups/lumen/20261028T104047Z
Source: WP-CLI: wp config set
db.sql.gz: OK
files.tar.gz: OK
manifest.txt: OK
Downloading from https://wordpress.org/wordpress-7.1.3-no-content.zip ...
md5 hash verified: 8afdbea5f8cfd97d3739dae451340ade
Success: WordPress downloaded.
Success: Imported from 'STDIN'.
Success: WordPress installation verifies against checksums.
Success: WordPress database already at latest db version 61833.
Success: The cache was flushed.
Success: Rewrite rules flushed.
https://lumenpress.test
real 0m 10.34s
The restored site had 3,000 lp_book posts, 60 attachments with 360 files in wp-content/uploads/2026/10/,
lumen-catalog active, the /%postname%/ permalink structure, and book permalinks such as
https://lumenpress.test/books/post-2999/. A backup with one altered byte in files.tar.gz was rejected at the first
step (files.tar.gz: FAILED, exit status 1) before anything was written.
After a restore
-
Plugin integrity:
wp plugin verify-checksums --allcompares plugins from the WordPress.org directory with their published checksums. Custom and commercial plugins have none; forlumen-catalogit prints "Could not retrieve the checksums for version 0.1.0 of plugin lumen-catalog, skipping" and reports "Verified 2 of 3 plugins (1 skipped)", so those plugins are checked against their own repositories instead. -
Caches: flush the persistent object cache (
wp cache flush), any page cache and the CDN, or visitors keep seeing pre-restore pages and the object cache keeps pre-restore options. A Redis object cache does not need to be backed up; it is rebuilt from the database. -
After a compromise: restore from a backup made before the intrusion, update core, plugins and themes, change all passwords and rotate the authentication keys and salts with
wp config shuffle-salts, which logs everyone out (see Security Hardening). Restoring the same vulnerable versions only resets the clock. -
Different URL: a restore to another domain (staging, a new host name) needs
wp search-replaceon the database, which handles serialized data; that is a migration, covered in Importing, Exporting and Migrating. -
Site Health: open Tools > Site Health to confirm loopback requests, scheduled events and the object cache drop-in, then log in, open a post in the editor and load a few front-end pages.
When restoring in place on a running server, put the site into maintenance mode first so that visitors and cron do not write to a half-restored database, and take one more backup of the broken state for later analysis:
wp maintenance-mode activate
wp db export /var/backups/lumen/pre-restore-$(date -u +%Y%m%dT%H%M%SZ).sql --single-transaction
gunzip -c /var/backups/lumen/20261028T104047Z/db.sql.gz | wp db import -
wp cache flush
wp maintenance-mode deactivate
Source: WP-CLI: wp maintenance-mode
wp maintenance-mode activate writes the .maintenance file in the root, which makes WordPress answer every request
with a 503 "Briefly unavailable for scheduled maintenance" page (or wp-content/maintenance.php, if present).
Testing restores and measuring RTO
The manual upgrade instructions say to verify that the backups exist and are usable, and call that essential. A checksum proves the archive was not damaged after it was written, and a backup plugin’s "verification" usually proves the same; neither proves the archive contains what is needed. Only a restore does. Schedule a restore test (monthly for Lumen Press, and after any change to the backup scripts, the hosting or the database version) into an isolated environment, then check content counts, a login, the editor and a few pages, as above.
The test also produces the RTO figure. On the test site the restore took about 10 seconds; the parts that grow with the site are the downloads of the backup from off-site storage (network-bound, roughly proportional to the uploads), the database import (index rebuilds, which dominate above a few gigabytes) and cache warm-up. The parts that do not show in a scripted test are often longer: noticing the outage, getting access to the off-site storage and the decryption key, provisioning a server and changing DNS (whose old records stay cached for their TTL). Write the steps down as a runbook, time a full drill once or twice a year, and compare the total with the RTO; if it does not fit, the answer is a standby environment rather than faster backups.
Backup plugins and host backups
Plugins are the usual choice on shared hosting without SSH. Common ones in the directory are UpdraftPlus and BackWPup (scheduled database and file backups to remote storage), All-in-One WP Migration (whole-site export and import, mostly used for migrations) and WP-DB-Backup (database only, the example used in the official database guide). Jetpack VaultPress Backup is a commercial service with off-site, real-time backups; commercial products change names and owners over time (the BackupBuddy plugin named in one older book is sold under another name today). Whatever the plugin, check where archives are stored (not under the web root, not only on the same server), whether remote-storage credentials sit in the database where a compromise exposes them, that scheduling depends on WP-Cron, that large sites hit PHP time and memory limits, and that a restore works on a clean server.
Host backups (daily snapshots in the hosting panel) are a useful extra copy, and older books rely on them. They are usually kept on the same provider, sometimes the same infrastructure, for a limited time; restoring them may be a support ticket; and they disappear with the account. Find out the retention, whether they can be downloaded, and how long a restore takes, and keep an independent off-site copy as well. The official guide also notes that cPanel’s full account backups are archival and cannot be restored through cPanel itself.
The rollbacks WordPress performs on its own are not backups either. Since 6.3 a failed manual plugin or theme update
is rolled back from wp-content/upgrade-temp-backup/, and since 6.6 a plugin auto-update that breaks the site is
rolled back, but neither covers content, settings, a bad but successful update or a lost server (see
Updates and Auto-Updates).
Disaster recovery and failover
Disaster recovery is the plan for getting the site back after a major failure; failover is switching automatically to a standby component when the active one fails. Failover reduces RTO for infrastructure failures, but it replicates mistakes: a dropped table or a defaced page is copied to the replica within seconds. A standby never replaces backups.
| Incident | Recovery | What limits RPO and RTO |
|---|---|---|
Bad plugin or theme update |
Roll back the code (previous release, |
Whether the update changed the schema; whether a pre-update backup exists |
Content deleted or corrupted by a user |
Restore single posts from the Trash (30 days by default) or revisions; otherwise import the backup into a scratch database and copy the rows back |
Backup frequency; time to find the right rows |
Hacked site |
Restore from a backup made before the intrusion onto a clean server, then patch and rotate credentials |
Knowing when the intrusion started; retention long enough to go back that far |
Database server lost |
Promote a replica, or restore the last dump (plus binary logs) on a new server |
Replication lag or dump frequency; dump size |
Provider or region lost |
Rebuild in another region or provider from off-site backups and infrastructure-as-code, then switch DNS |
Off-site copy freshness; DNS TTL; access to keys |
For an RTO of minutes, Lumen Press would run a warm standby: a database replica and a synchronised copy of
wp-content (or shared object storage for uploads) in a second location, web nodes built from the same images, and a
health-checked load balancer or DNS failover in front. The design of stateless web nodes, shared uploads and database
replication is covered in Scaling and High Availability; for
containerised sites, the volumes that hold the database and wp-content are the data to back up (see
Storage: Volumes, Bind Mounts and tmpfs).
On Multisite, the database contains the network tables and one set of tables per site, and uploads are under
wp-content/uploads/sites/<id>/, so the whole-network backup above covers every site. Restoring a single subsite means
importing its wp_<id>_* tables and its uploads directory while leaving the network tables untouched; see
Multisite Networks.
Where older books are outdated
| Older advice | Current practice |
|---|---|
A database backup made with phpMyAdmin is enough before upgrading |
The official upgrade instructions require a backup of the database and of all files, including |
phpMyAdmin Export is the standard way to back up the database |
It still works for small sites, but it cannot be scheduled and fails on large databases. |
Back up |
|
Weekly backups suit most sites |
Frequency follows the RPO of each part. A weekly database backup can lose a week of posts, comments and orders; the database is small and cheap to copy often. |
Backup plugins verify the integrity of backups |
A checksum or a plugin’s check proves the file is intact, not that it restores. Only a restore test proves the backup and gives the RTO. |
Daily host backups make your own backups unnecessary |
Host backups are a good extra copy but share the provider’s fate and retention limits. Keep an independent, encrypted off-site copy. |
|
On MariaDB 11 the tool is |
Common errors and pitfalls
-
Dump files in the web root.
wp db exportwithout a path writes into the current directory, often the document root; a leftoverlumen-2026-10-28-xxxx.sqlis downloadable by anyone who guesses the name. -
A pipeline that hides failures. Without
set -o pipefail, a failed export piped intogzipexits 0. Verify withgzip -tand alert on unusually small dumps. -
Character set damage. Importing with a client set to
latin1mangles accented characters and emoji; keeputf8mb4on both ends (WP-CLI andmariadb-dumpuse it by default). -
Missing drop-ins or must-use plugins. A backup of
plugins/andthemes/only losesobject-cache.phpandmu-plugins/, and the restored site behaves differently from the original without any error. -
URL changes done with SQL
REPLACE. Serialized options and widgets break; usewp search-replace.
Related pages
-
Importing, Exporting and Migrating: moving a site to a new host or domain with
wp search-replace, and why a WXR export is not a backup -
Database Schema: the tables a dump contains
-
wp-config.php Reference: database constants and salts
-
Security Hardening: recovering from a compromise
-
Updates and Auto-Updates: rollbacks and pre-update backups
-
Scaling and High Availability: replicas and standby nodes
-
WP-CLI: the commands used by the scripts