← Back to blog

How to clone a WordPress site without the copy touching production

The SandyWP team 7 min read

Cloning a WordPress site is two operations that every guide covers, files and database, and a third that almost none of them mention: cutting the copy's connections to everything the original talks to. A clone carries the live API keys, the same webhook registrations, the same storage bucket and the same schedule of cron jobs, and none of that is a URL, so search-replace never touches it.

The result is a site you made to be safe that can charge a real card, empty a production search index, or overwrite the object your live media library serves. This post is about that third operation.

We fetched the three guides ranking for this phrase before writing (BlogVault, Jetpack, TeamUpdraft, read 2026-09-05). Between them they run about 10,000 words on copying files and rewriting URLs.

Not one mentions payment keys, webhooks, offloaded media, license activations or backup destinations on the copy.

What a clone actually copies

Files and a database. That is the honest description, and it is the source of the problem, because the interesting configuration in WordPress lives in the database as opaque values.

wp_options holds API keys, OAuth refresh tokens, S3 bucket names, license keys, SMTP credentials and index identifiers. A serialization-aware search-replace rewrites the hostname and leaves every one of those exactly as it found them.

So the copy is not a site that resembles production. It is a second site authenticated as production, to every third party production uses.

The things that stay wired to the original

Payment gateways in live mode

The clone has the live secret key, so a checkout on it is a real charge. That much people expect.

The part that surprises is the direction of travel. Gateway webhooks are registered at the gateway, pointed at production's endpoint, and cloning does not create a second registration.

If your clone creates a subscription or a payment intent, the gateway sends the event to your live site, which processes it against a customer record that does not exist there.

WooCommerce's own testing documentation puts it plainly: "Many payment gateways offer a sandbox or testing mode that allows you to process test payments without incurring fees or charges. Activate this mode when testing your orders and checkout."

Do that on the copy before you do anything else, and be aware that some extensions store their mode as a serialized setting rather than a checkbox you can find.

Media that was offloaded

If the site uses WP Offload Media, S3, or any CDN-backed uploads plugin, the copy holds the same bucket name and the same credentials.

Nothing warns you. The clone's media library renders correctly, because it is reading production's bucket.

Then someone regenerates thumbnails on the copy, or deletes an attachment to test the flow, and the object disappears from the live site.

Change the bucket, or remove the credentials and let the copy serve local files. Both are fine.

Leaving it is not.

Search indexes

Algolia, Elasticsearch and their WordPress integrations key on an index name that is stored in the database and copied verbatim. The clone reindexes on activation or on a content change, and production's index is now the clone's content.

This one is worth checking first because it fails destructively and silently, and because reindexing is exactly the sort of thing you do on a test copy without thinking.

Backup destinations

The copy holds the credentials for the remote storage the original backs up to, and often the same schedule.

A backup written by the clone into that destination is not obviously distinguishable from a real one.

If the retention policy prunes by count, the clone's backups can push genuine restore points out of the window. That is a bad thing to discover during a recovery.

License activations

Premium plugins commonly bind an activation to a domain and cap the count. A clone that phones home either burns a seat or, on some licensing systems, deactivates the original when the same key checks in from a new host.

If a premium plugin stops updating on the live site a day after you cloned, this is why.

Cron, which starts on its own

Every scheduled event in the source database comes across with its next-run timestamp, so the copy inherits the whole schedule.

Nobody triggers it. It fires when someone loads a page.

That is the renewal job, the ERP sync, the abandoned-cart email, the inventory push. All of them running with production's credentials against production's systems, from a site you were treating as scratch.

wp cron event list on the copy shows what is queued and when. Read it before the first pageview, not after.

The order that keeps this cheap

Do the disconnecting before you look at anything, not after. The failure mode of this whole class of problem is that a single admin pageview is enough to start cron, and a clone is useless if you cannot open it.

  1. Get the copy onto a host with no shared state with the original. Different server, different database server, different filesystem.
  2. Before the first admin visit, clear the schedule: wp cron event delete --all or set DISABLE_WP_CRON in wp-config.php. Re-enable it deliberately when you are testing cron itself.
  3. Stop mail leaving. On a copy, wp_mail() still has the SMTP credentials and the customer addresses.
  4. Then go through the credentials: gateways to test mode, bucket names changed, index names changed, backup destination removed, license keys cleared.
  5. Only then log in and use the site.

A quick way to find what you are looking for, since these values are scattered across plugins:

wp option list --search='*_key' --fields=option_name
wp option list --search='*secret*' --fields=option_name
wp option list --search='*bucket*' --fields=option_name
wp cron event list

That is a starting point rather than an audit. Plugins store credentials inside serialized settings arrays under names like woocommerce_stripe_settings, so grep the option names first, then read the ones that look plausible.

Why the environment does more work here than the checklist

Every item above is a thing you can forget. A checklist that has to be run perfectly, by hand, every time, on a copy that is by definition disposable, is not a control.

The structural version is to clone into an environment that cannot reach production even if you miss one. That is what a disposable install buys you, and it is the reason we built the Cloner plugin to push a copy into a sandbox rather than onto a subdomain of the same server.

Two of those failure modes are closed by the environment rather than by you. Mail is one: SandyWP intercepts wp_mail() before it leaves and records it in the Email Log, so with no SMTP plugin installed a copied site cannot reach a real inbox no matter which plugin decides to send.

The other is the shared-server class of problem, since each sandbox runs in its own container network with its own database server, so there is no shared object cache and no shared cron daemon to inherit.

The rest, the gateway keys and the buckets and the index names, is still on you. No environment can tell a legitimate outbound API call from a catastrophic one.

Cut the credentials.

For working through them, the shell is faster than the admin: SSH and SFTP give you a real WP-CLI on the copy, which is how the commands above are meant to be run.

When you should not clone at all

If what you are testing does not need the content, do not copy the data. A clean install with the same plugin set and the same PHP version carries none of this risk, and for a plugin conflict or a theme change it answers the question just as well.

If what you are testing is a client handoff or a demo, a clone of the live site is the wrong starting point twice over, once for the credentials and once for the customer data. We wrote separately about what a demo site sends outward.

And if you need the production data specifically, scrub it. That is a different job from disconnecting it, and it has its own post.

The honest limit: a clone that has been disconnected properly is no longer a rehearsal of production's integrations. You can test your code against it, and you cannot test the Stripe webhook path on it.

Those need the gateway's own test mode, pointed at the copy, which is work.

The short version

Files and database are the easy two thirds. The third is that the copy inherits every credential production holds, and none of them are URLs, so nothing in the clone process rewrites them.

Cut cron and mail before the first pageview. Then gateways, buckets, indexes, backup destinations and licenses, in that order, because they fail in that order of expense.

If you want the copy somewhere it cannot reach production by construction, that is what SandyWP does, and the first one takes about a minute.