← Back to blog

How to create a WordPress staging site, and how to check it is really a copy

The SandyWP team 8 min read

There are three ways to create a WordPress staging site: your host's staging button, a cloning plugin like WP Staging, or a separate install you copy the site into. All three take a few minutes, and the rest of this post is about the part that decides whether the test means anything.

A staging site is only useful to the degree it matches production. Every guide that ranks for this phrase stops at the button. None of the four we read covers PHP version parity, and only one mentions caching at all, which means a lot of people are testing against an environment that differs from their live site in ways they never checked.

The three methods, and when each one is right

Your host's staging button. If your host has one, use it. It copies the files and the database, sets up the URL, and gives you a push-to-live path back. That last part is the real value, and neither of the other two methods gives you it.

The catch is that the copy is usually made on the same server, sharing the same PHP version and often the same object cache. That is good for parity and bad for testing a PHP upgrade, which is the one test it cannot do.

A cloning plugin. WP Staging and similar plugins clone into a subdirectory or subdomain of the same site. Works on hosts with no staging feature, and it is the cheapest option.

It also runs inside the site you are trying not to break. A clone that shares the WordPress install, the server, and often the database server is a weaker isolation boundary than it looks.

A separate install you copy into. Slowest to set up by hand, and the only one that lets you change the stack underneath the site. If you need to answer "does this work on PHP 8.4" or "does this survive WordPress 7.1", this is the method, because the other two hold the stack fixed.

This is the category SandyWP is in. The Cloner plugin packages your live site and ships it to a disposable install, your production site is only read, and you pick the PHP and WordPress version on the other end.

Why "it worked on staging" is not the same as "it works"

Six things move between production and a staging copy. Each of them can make a test pass that should have failed.

1. The PHP version

A copy made on the same server inherits the same PHP. A copy made anywhere else inherits whatever the new environment defaults to, which is frequently newer.

Both cases are wrong in the same way: the version was inherited, not chosen. If you did not decide what PHP the staging site runs, you are not testing what you think you are testing.

Check it first, before anything else:

wp eval 'echo PHP_VERSION . PHP_EOL;'
wp eval 'print_r(get_loaded_extensions());'

The extension list matters as much as the version. A site that depends on imagick, intl or soap will throw on staging or, worse, quietly fall back to a different code path.

On SandyWP the version is a dropdown on the sandbox, covering 8.5 down to 7.4, and switching restarts the container without touching files or the database. That is deliberate: testing on old PHP is a real job, and it needs an environment that will run it.

2. Caching

WP Engine's own staging documentation says it plainly: staging sites may not be exact replicas, and caching is usually not enabled on one.

That cuts both ways. With caching off, a bug that only appears when a page is served from cache never shows up. With caching on but cold, your first pageview is not the pageview a returning visitor gets.

Object caching is the sharper edge. If production runs Redis or Memcached and staging does not, then any code that leans on a persistent object cache behaves differently, and any transient bug you are chasing may simply not reproduce.

3. Email

This is the one that causes actual harm. A copied site has your real customer records, your real order data, and by default a working mail function.

Run a WooCommerce test order on a naive copy and a real person gets an order confirmation. Trigger a password reset test and a real person gets a reset link. Let WP-Cron catch up on a backlog and a batch of them go out at once.

Setting the site to "discourage search engines" does nothing about this, and neither does the staging button on most hosts. You have to disable outbound mail explicitly, or run somewhere that does not deliver.

SandyWP intercepts wp_mail and stores the rendered message instead of sending it, which is what Email Log is. You can read the receipt and confirm the form fired, and nobody outside receives anything. We wrote up the two ways test-site email goes wrong separately, because the opposite failure (nothing sends and you assume it worked) is just as common.

4. WP-Cron

WP-Cron fires on pageviews. A staging site nobody visits runs no scheduled events at all, so a broken scheduled task looks fine because it never ran.

The reverse happens on first load: every overdue event in the copied cron array fires at once. If those events send email or charge cards, see the previous section.

List what is actually scheduled rather than assuming:

wp cron event list
wp cron event run --due-now

Running them on purpose, once, is the test. Waiting for them is not.

5. Third-party credentials

Your copied database contains live API keys. Payment gateway secrets, shipping account credentials, CRM tokens, webhook signing secrets, all of it, all still pointing at production.

A test checkout on staging with live Stripe keys is a real charge. A sync test with a live CRM token writes real records. Swap the keys to test-mode ones before you touch anything that transacts, and treat that as part of creating the staging site rather than as an afterthought.

The same applies to customer data generally. Our post on testing with a copy of a live site without leaking customer data covers the rest of it, including the secrets that live in wp_options and the invoices sitting in wp-content/uploads.

6. Indexing

Discouraging search engines sets a noindex meta tag and a robots.txt rule. It is a request, and it is the only protection most staging guides mention.

HTTP authentication in front of the whole staging site is the actual answer, because it works on crawlers that ignore robots.txt and on anyone who finds the URL in a shared link.

A five-minute parity check

Run this against the staging site the moment it exists, before you make the change you built it for. Every command is WP-CLI, which is preinstalled on a SandyWP sandbox and reachable over SSH or the CLI.

# stack
wp core version
wp eval 'echo PHP_VERSION . PHP_EOL;'
wp db query "SELECT VERSION();"

# what is actually active here versus production
wp plugin list --status=active --field=name
wp theme list --status=active --field=name

# the URL rewrite landed everywhere
wp option get siteurl
wp option get home
wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%//your-live-domain.com%';"

# scheduled work
wp cron event list --fields=hook,next_run_relative

# nothing broken on the way in
wp core verify-checksums
wp db check

Run the same first block on production and compare the two by eye. If any line differs and you did not intend it to, you have found the thing that will make your test lie to you.

The post_content query is the one people skip. A staging copy with a half-finished search and replace still loads fine, because WordPress resolves most of what it needs from siteurl, and the leftover absolute URLs only surface later. If the migration went through a database export, also check that serialized values survived it, because that failure is silent by design.

When staging is the wrong tool

Staging is the right tool when you need to test a change and then ship that exact change to production. The deploy-back path is the whole point, and a disposable copy does not have one.

A disposable copy is the right tool when the thing you are testing is the environment itself, or when you need more than one at a time. Testing the same plugin update against PHP 8.1, 8.3 and 8.4 is three environments, and your host gives you one staging slot.

It is also the right tool when the copy should not survive. A support reproduction, a client demo, a "does this plugin conflict with that one" question: none of those deserve a permanent second site with real credentials in it, and a template you launch fresh each time is both faster and safer.

For agencies and anyone doing upgrade testing the honest answer is that you want both. Keep the staging site for the change you are going to deploy. Use a throwaway copy for everything you are only going to look at.

The short version

Making a staging site is the easy part, and it is the part every guide covers. Checking that it is a copy is the part that decides whether the test was worth running.

Before you trust a result from staging, confirm the PHP version was chosen rather than inherited, confirm outbound email cannot reach a real person, run the cron events instead of waiting for them, and swap any live third-party key for a test one. If you skipped any of those, you tested something, but not your site.

If you want the copy with the stack under your control, the Cloner makes one from your live site in a click, and debug mode is a toggle rather than a wp-config edit.