Restore to Staging First, Then Decide About Production
A restore overwrites the only site you have. Why the safe order is staging first, what it catches, and when a direct restore is the right call.
Martin Kiel · @mkiel
WordPress internals
- Topic
- WP
- Section
- WordPress Internals
- Published
- 20 OCT 2026
- Reading time
- 7 MIN
- Words
- 1650W
The update broke the checkout. The fastest fix looked obvious: import yesterday's database, because yesterday it worked. The import took four minutes and the checkout came back. Two days later an editor reported that a post published this morning had disappeared, a customer's order was missing, and the homepage was showing last week's prices. The restore had worked. It had also rolled the site back to yesterday, and nobody had told the people writing on it.
That is the ordinary argument for restoring to staging first. A restore is not a repair. It is a replacement of the current state with an older one, and everything that happened between the two disappears. On a site with one author and no orders, the loss is small and the decision is easy. On a site with editors, customers and integrations, the decision needs a rehearsal.
Staging is that rehearsal. It costs an hour and a second database, and it converts a guess into a list of things that will break.
Disclosure: some links on this page are affiliate links, and WPSifero may earn a commission if you buy through them. That does not change the price, and the order of operations below is the same whether the staging copy comes from a host, a plugin or a local environment.
A direct restore assumes the backup is the only thing wrong
Every restore carries an assumption: the site is broken because of something that happened after the archive was taken, and putting the archive back removes the cause. That is true for a bad update, a deleted page and a mistaken settings change. It is false for a compromise, a failed migration and a hosting change, which are the three situations where a restore is most likely to be attempted under pressure.
In a compromise, the archive may already contain the attacker's work. Restoring it reproduces the infection and resets the clock, and the site is compromised again on a delay. In a failed migration, the archive may predate a database change the new code depends on. In a hosting change, the archive may be perfect and the environment around it different: a different PHP version, a missing extension, a different mail path.
Staging turns each of those from an assumption into an observation. The restored copy either contains a suspicious administrator or it does not. The checkout either reads the custom table or it throws. The mail either sends or it lands in a log. None of that can be learned from the production site while it is being overwritten.
Staging catches what a restore hides
The failures that staging catches are rarely the ones people expect. The obvious ones, a missing file or a broken theme, show up immediately. The expensive ones are quiet for a week.
| Failure | Looks like | Found by |
|---|---|---|
| Schema drift | A settings screen that saves and discards | Opening the plugin screen, saving |
| Missing custom tables | Orders absent from reports | Placing a test order |
| Domain-locked licence | Updates stop, a banner appears | Checking the licence screen |
| Serialized URL damage | Widgets and menus empty | Loading the homepage and a menu |
| Mail configuration loss | Password resets never arrive | Sending a reset |
| Stale object cache | Old content after a correct restore | Flushing and reloading |
Domain-locked licences deserve a note because they are invisible until they matter. A premium plugin or theme activated on the production domain often refuses updates, or degrades, on a staging hostname. That is not a reason to skip staging; it is information. The staging copy needs either a development licence, a licence that allows staging, or an understanding that updates on staging will be manual. Discovering this during a drill is cheap. Discovering it during a restore, when the licence server is the one thing you cannot reach, is not.
Restore the archive, not a copy of the broken site
The staging copy must be built from the archive, not from a fresh duplicate of production. Cloning the live site copies the problem, which is fine for testing a fix and useless for testing a restore. The sequence is the same one the monthly drill uses: empty database, import the dump, extract the files, point the configuration at the staging target.
# On the staging install, with an empty database.
wp db import /tmp/site-db.sql
tar -xzf /tmp/site-files.tar.gz -C /var/www/staging
wp config set DB_NAME staging_db --raw
wp config set WP_ENVIRONMENT_TYPE staging
wp cache flush
Then rewrite the URLs before opening the site. If the archive was taken from https://example.com and it is being restored to https://staging.example.com, every option, post and widget that stores a URL needs the new host. Doing this after the first page load leaves redirects and mixed content in the browser cache, which makes the next hour confusing for no reason.
One more detail: if the archive is a full-account snapshot, it may include the old wp-config.php with the production database credentials. Replace it deliberately, and rotate any key that the archive exposes. A staging copy that still holds production payment credentials is a production site with a different hostname.
Rewrite URLs without corrupting serialized data
WordPress stores more serialized data than people expect. Widget options, theme mods, page builder content, some plugin settings and parts of WooCommerce all keep arrays and objects in the database, and those structures record string lengths. A plain SQL replace changes the URL inside the string without changing the length prefix, and the value becomes unreadable. The site does not report a database error. It renders an empty sidebar or a blank page builder layout.
Use a tool that unserializes, replaces and reserializes:
wp search-replace 'https://example.com' 'https://staging.example.com' \
--all-tables --precise --skip-columns=guid
The --precise flag makes the command slower and safer, which is the right trade for a one-off. The GUID column is skipped because it is an immutable identifier used by feeds and some integrations, not a URL to be kept current. If the site uses a page builder with its own encoded format, check one page of each template after the replace; a builder that stores base64 or JSON needs its own migration path, and the command will not know that.
Search-replace is not reversible
Take a database dump before running it, even on staging. A replace with the wrong source string, or run twice with overlapping domains, produces a site that is tedious to repair by hand.
After the replace, flush the object cache and the page cache, then reload. A correct database with a stale cache looks exactly like a failed restore, and the difference costs ten minutes of confusion if the flush is not part of the sequence.
Keep staging out of production's services
A restored copy is a working site, which means it will try to do working-site things. It will send mail, run cron, call payment webhooks and post to the analytics property. All of that is harmless in a sandbox and damaging when the sandbox shares credentials with production.
Cut the connections before the first page load. Set WP_ENVIRONMENT_TYPE to staging, discourage search engines, put basic authentication in front of the site, and route mail to a log rather than to an SMTP provider. Replace payment keys with test keys. Point webhooks at a request bin. Disable the cron runner or run it manually, because a restored site will happily process a queue of real orders.
// wp-config.php on the staging copy only.
define( 'WP_ENVIRONMENT_TYPE', 'staging' );
define( 'DISALLOW_FILE_EDIT', true );
// Route mail to a log plugin. Never to the production SMTP account.
Search engines are the other leak. A staging site that is indexable competes with production for the same content and can outrank it for a while. Basic authentication is the reliable control; a noindex tag and a robots.txt are useful backups for the case where authentication is removed later and nobody remembers why it was there.
When a direct restore is the right call
Staging is the default, not a rule. When the site is down and losing money by the minute, the arithmetic changes. The sequence that keeps a direct restore defensible has three conditions.
The first is a known-good archive. Not "the newest one", but one that has been restored before, or one taken before a change the operator can name. The second is a snapshot of the broken state, taken before the restore, so the failure can be investigated afterwards and the restore can be undone if it makes things worse. The third is a written note of what will be lost: orders since the archive, posts since the archive, anything a person is currently editing.
With those three in place, a direct restore is a calculated rollback. Without them, it is a bet made under pressure. The distinction is not about courage or speed; it is about whether the loss is known before it happens.
The decision rule
Restore to staging unless the site is down, the archive is verified and a snapshot of the broken state exists. If all three are true, restore directly and write down what was lost. If any is false, staging first, even if it costs an hour.
Two products shorten the staging step rather than replacing it. Pressable includes staging environments in its hosting, so the copy is a button and a database sync. Jetpack's backup service can restore into a new location without touching the live site, which is useful when the hosting account itself is the thing that failed. For a small site, a local environment and WP-CLI do the same job for nothing, and the local environment guide is the cheaper path.
The habit that matters is the one before the restore: take a copy of the broken site, name what will be lost, and only then decide where to restore. A restore is a time machine with no undo. The staging copy is how you find out what the trip will cost before taking it.
Martin Kiel · @mkiel
WordPress internals
Writes about the load-bearing parts of WordPress: the options table, hook ordering, WP_Query behaviour and the database work that decides whether a plugin scales or falls over.
Continue