Restore the backup onto an empty WordPress on the same PHP and WordPress version as production, then verify it with counts rather than by looking at it. Anything less is testing something other than recovery.
The standard advice is "test your backups on staging". That advice is right about the frequency and wrong about the target, because staging is already a copy of your site, and a restore that lands on top of a working install is a very different operation from one that lands on bare metal at 2am.
We build SandyWP, a disposable WordPress sandbox, so we have an obvious interest in you having somewhere throwaway to do this. The routine below works on any environment you can create and destroy cheaply. The SandyWP-specific parts are marked.
Why restoring onto staging is not a restore test
A real recovery starts from nothing. The database is gone, or the filesystem is gone, or the host has handed you a fresh account and a support ticket number.
Your staging site is not nothing. It already has a working wp-config.php with credentials that resolve, a database that exists, a WordPress core that is already installed, and probably a .htaccess your backup does not contain.
So a "successful" restore onto staging can quietly borrow all of that from the environment. The classic version of this is a backup that never actually contained wp-config.php, which every backup plugin excludes by default for good security reasons, and which restores perfectly onto a site that already has one.
The second borrowed thing is the database user. If your archive contains a SQL dump but no record of how WordPress connects, restoring onto an environment with working credentials proves the dump imports, not that you can rebuild the connection.
Neither of those shows up when you restore onto staging and click around. Both show up immediately on an empty install.
Check the four ranking guides do not cover this
We read the guides that currently rank for this phrase before writing. BlogVault's, OSTraining's and WPExplorer's are all solid, roughly 2,000-word walkthroughs, and all three land on the same two methods: use a backup plugin's own test-restore button, or import the archive into Local.
Both methods work, and the plugin route is genuinely convenient. BlogVault's test restore even lets you pick the PHP version, which is more than most.
What none of them cover is the part that decides whether the test means anything: restoring onto empty versus onto an existing install, the PHP limits that truncate a restore halfway, and any objective check beyond "visit the homepage and see that it loads".
That last one matters most. A homepage that loads is compatible with a database that lost its wp_postmeta table.
Match the environment, or you tested a migration
If your production site runs PHP 8.1 and WordPress 6.9, and you restore the backup onto PHP 8.3 and WordPress 7.0, a failure tells you nothing useful. You cannot tell whether the backup is bad or the version jump is.
That is a fine test to run, and it is a useful one. It just is not a backup test, it is an upgrade test, and mixing the two is how people conclude their backups are broken when they are not.
So pin both. Find out what production is actually running:
wp core version
php -v
In SandyWP you pick both when you create the sandbox. WordPress can be latest (currently 7.1), 7.1, 7.0, 6.9, 6.8, 6.7 or 6.6, and PHP can be 8.5, 8.4, 8.3, 8.2, 8.1 or 7.4. The default is PHP 8.3, so if production is on 8.1 you have to say so.
Once the sandbox is ready you can change PHP without rebuilding it. Site settings, then PHP configuration, then pick a version. It restarts for a few seconds and leaves your files and database alone, which is useful when you want to re-run the same restore on two runtimes.
The limits that break a restore halfway
Most restore failures we see are not corrupt archives. They are the restore process hitting a PHP ceiling and stopping, sometimes without saying so.
Three settings do almost all of the damage:
upload_max_filesizeandpost_max_size, if you are uploading the archive through wp-admin. A 900 MB archive against a 128 MB limit fails at the browser, which is at least loud.max_execution_time, which is the quiet one. A large SQL import gets killed partway through, and you are left with the first two hundred tables of your database and a site that half works.memory_limit, which usually shows up as a white screen during the unpack rather than an error you can read.
Set these deliberately before you start, not after the first failure. In a SandyWP sandbox they are all in the PHP configuration panel: max execution time up to 300 seconds, memory limit up to 512 MB, and post max size and upload max filesize up to 5,120 MB each. The upload limit cannot exceed the post-size limit, which is the usual reason a change appears not to take effect.
Turn on Debug mode at the same time so that a fatal during the import lands in debug.log where you can read it, instead of becoming a blank page.
Getting a multi-gigabyte archive into the test site
Uploading through wp-admin is the fragile path for anything large. Two better routes, both of which avoid PHP limits entirely for the transfer itself.
Over SSH, pull the archive down inside the sandbox from wherever it already lives:
sandywp ssh my-restore-test
curl -o /tmp/backup.zip "https://your-storage/backup-2026-08-19.zip"
Or mount the sandbox as a local folder with the CLI and copy the file in with Finder or cp:
sandywp mount my-restore-test
cp ~/Downloads/backup-2026-08-19.zip ~/SandyWP/my-restore-test/
Both need a paid plan and a ready sandbox. There is also a browser File Manager with an upload button if you would rather not install anything, which is fine for smaller archives.
Watch your storage allowance while you do this. Plans run from 5 GB on Plus to 250 GB on Workspace Scale, and an uncompressed restore of a 4 GB archive briefly needs room for both copies.
Verify with counts, not with your eyes
This is the part that turns a demo into a test. After the restore finishes, compare the sandbox to production with numbers.
Run these on both, and diff the output:
wp post list --post_type=any --format=count
wp user list --format=count
wp option get siteurl
wp plugin list --status=active --field=name
wp db size --tables --format=csv
wp db size --tables is the one that catches truncated imports. A missing table, or one that is a tenth of the size it should be on production, is invisible from the front end and obvious in that list.
Then check the database is structurally intact rather than merely present:
wp db check
And confirm the core files are the real ones, which matters if your backup captured a site that was already compromised:
wp core verify-checksums
That last command "verifies WordPress files against WordPress.org's checksums", per the WP-CLI documentation. Add --include-root and it will also warn about non-WordPress files sitting in the root directory, which is how a restored backdoor announces itself.
Finally, click the five things that make the site money. Numbers catch a broken restore; a human catches a restore that is technically complete and visually wrong because the theme's custom tables were not in the archive.
What this test still does not prove
Being honest about the boundary is the point, because a test you trust too much is worse than no test.
It does not prove your host can restore. You proved the archive is complete and importable. Whether your hosting provider can get you a working server to import it onto, and how long that takes, is a separate question and usually the longer half of the outage.
It does not test anything domain-bound. Payment gateways, OAuth callbacks, licence servers and email deliverability all key off the domain, so they will not work on a sandbox URL regardless of how good the backup is.
It does not test the host layer. Object cache, edge cache, WAF rules, open_basedir and cron configuration are not in your backup and are not in the sandbox either. If your site depends on a Redis instance, restoring the files and database does not bring it back.
It does not test at production load. A sandbox is a single container. It tells you the data is there, not that the restored site survives your Monday traffic.
And a restored copy carries your real user table. Treat the sandbox as production data: give it a short lifespan, do not share the URL, and delete it when the test is done.
How often to actually do this
Quarterly is the number most of the guides land on, and it is a reasonable floor. The more useful trigger is event-based.
Run the test after anything that changes the shape of what is being backed up: a host migration, a PHP version change, adding a plugin that creates its own tables, or crossing a size threshold where the backup job starts running long.
If a backup job's duration or archive size changes noticeably from one week to the next, that is the signal to test, not the thing to ignore.
One thing SandyWP does not do
To be clear about our own product: SandyWP does not ingest a third-party backup archive for you. There is no "upload your UpdraftPlus zip" button.
What it gives you is the empty, version-matched, disposable WordPress to restore onto, plus SSH and file access to get the archive in and WP-CLI to verify the result. The restore itself is done by whatever plugin or process made the backup, which is correct: you want to test the tool you would actually reach for in a real outage.
Our Cloner plugin is a different thing and worth not confusing with this. It copies a live site you control straight into a sandbox, which is the right tool for testing an update or reproducing a bug. It is not a test of your backup, because it does not touch your backup.
If you run sites for other people, this is one of the routines worth making boring and repeatable, alongside the update testing on our agencies page. The cost of finding out your backups are unrestorable is fixed. Only the timing is negotiable.
