Pushing updates straight to production is the cheapest way to break a WordPress site. A plugin update conflicts with the theme, a security patch interacts badly with a page builder, a PHP version bump pushes a deprecated function off a cliff, and suddenly the live site shows a white screen or, worse, a half-broken checkout that still takes orders. A staging environment is the cheap insurance against all of that. This article covers four ways to get one: through your managed host's built-in staging, through the WP STAGING plugin, through a manual clone on a subdomain, and through a local development tool. Each has different trade-offs and, importantly, different answers for the hardest part of staging: pushing changes back to production without nuking live data.
The scope here is deliberately narrow. It is a how-to, not a conceptual essay on deployment pipelines. If you want the background on wp-config.php constants or the mechanics of the WordPress URL search-replace, those live in dedicated articles linked inline where they become relevant.
What a staging environment does, and why you need one
A WordPress staging environment is a complete copy of your production site that lives at a separate URL, has its own database, and is not exposed to search engines. It exists so you can do three things that are unsafe to do on production: test plugin and theme updates before applying them, try out configuration or content changes before committing to them, and debug problems that only appear with real data. The WordPress handbook's environment type documentation introduced a WP_ENVIRONMENT_TYPE constant in WordPress 5.5 specifically so plugins and themes can detect that they are running on staging and disable anything that should never fire there (analytics, payment capture, transactional email, cache busting).
Staging is not the same as a backup. A backup is a point-in-time snapshot you restore after something breaks. Staging is an active environment you use to prevent the break in the first place. You need both.
Staging is also not the same as a local development environment. A local environment is where you build code. A staging environment mirrors production closely enough (server, PHP version, caching, CDN, database engine, data volume) that behaviour there is a reliable predictor of behaviour on the live site. The distinction matters because local tools like LocalWP and DevKinsta are great for development but they do not mirror production caching, load balancers, object cache, or CDN edge behaviour. For pre-deploy verification, you typically want staging on the same infrastructure as production.
Prerequisites
- A production site you control. You need either hosting-panel access, FTP/SSH access, or WordPress admin access depending on which method you pick.
- A recent backup of production. Every method below involves cloning the database, and every database clone is safer with a fresh backup sitting next to it. Take a database and files backup right before you start, regardless of any scheduled backups.
- A separate URL for staging. A subdomain (
staging.example.com), a separate domain (example-staging.com), or a hosting-provided staging URL. Running staging in a subfolder of the production domain is possible with WP STAGING Free but brings its own URL and redirect quirks. - Ability to edit
wp-config.php. At minimum, for settingWP_HOME,WP_SITEURLandWP_ENVIRONMENT_TYPEon the staging copy. - Ability to edit web server configuration (
.htaccessfor Apache, the server block for nginx), or a hosting panel that lets you enable HTTP basic auth on a subdomain. This is non-negotiable for the indexing-prevention step later. - WP-CLI access (optional but recommended). WP-CLI makes the search-replace step dramatically safer. See the dedicated WordPress search-replace guide for why.
Method 1: staging via your managed hosting control panel
If your host has a built-in staging feature, use it. It is almost always the fastest, most reliable, and least error-prone option, because the host handles the subdomain, SSL, file sync, database clone, search-replace, and noindex step for you in a single click.
Who includes staging as a standard feature:
- WP Engine includes development, staging, and production environments on all plans. You can create a staging copy from production with a single click, push files or database back, and swap environments. WP Engine's staging documentation covers the full workflow.
- Kinsta includes staging on all plans through MyKinsta, with a "selective push" feature that lets you push files and database independently back to live. This is the safest approach when you only need part of staging to go forward.
- Pressable includes staging on all plans. The interesting differentiator is two push modes: a data-sync push for selective changes and a domain-swap "flip" for full site overhauls.
- SiteGround, Bluehost, Flywheel also include one-click staging on most of their plans.
How to use it safely:
- In the hosting panel, create a staging copy from production. The host clones files and database to the staging environment and runs URL replacement so the staging domain is correct.
- Verify that the staging site loads at the staging URL and that the WordPress admin is reachable.
- Check that the host has added noindex protection (basic auth, X-Robots-Tag, or a bespoke gate). If not, add it yourself per the hiding staging from search engines section below.
- Set
WP_ENVIRONMENT_TYPEtostagingin the staging copy'swp-config.phpso plugins know where they are. Some hosts do this automatically. - Do your testing on staging.
- When you are ready to push changes back, use the host's push tool. For a WooCommerce store, always use selective push (files only, or files plus a specific subset of tables) never a full database overwrite. More on this in the "What NOT to copy back" section.
The main limitation is vendor lock-in. If you migrate away from the host, the staging feature goes with it. But for a managed WordPress customer, this is the cleanest setup there is.
Method 2: the WP STAGING plugin
WP STAGING is the most widely used staging plugin for WordPress. It clones the site into a separate copy that lives on the same server, typically at a subfolder URL like yoursite.com/staging-abc123. The clone runs on its own database tables (with a different prefix) and is isolated from production at the database level.
The critical distinction: WP STAGING Free clones to staging but cannot push changes from staging back to production. That is a Pro-only feature. This catches a lot of people off-guard. The documentation at wp-staging.com is clear about it, but the realisation usually lands after you have already built out a staging workflow around the free version. If your plan was "test the update on staging, then promote it to live", the free version will stop you at the second step, and you will be back to manually reapplying the change on production.
What Free does give you:
- A one-click clone into a separate subfolder URL with its own database tables.
- Selective exclusion of large directories (like
wp-content/uploads) at clone time. - Automatic noindex on the staging clone.
- Correct handling of the URL replacement inside serialised WordPress data during the clone.
What Pro adds:
- Push from staging to production, with selective sync of specific tables and files.
- Subdomain and separate-domain staging instead of the subfolder-only Free mode.
- Multisite support.
- WooCommerce-specific table exclusion on push (so you do not overwrite live orders).
- Scheduled backups.
How to use WP STAGING correctly:
- Install WP STAGING from the WordPress plugin directory on the production site.
- Go to WP STAGING Jobs in wp-admin and click "Create new staging site". Give it a name.
- On the scope screen, exclude
wp-content/uploadsif the uploads directory is large and you do not need the full media library on staging. Exclude any other large directories the same way. This dramatically speeds up the clone. - On the database-tables screen, include all tables unless there is a specific reason not to.
- Start the clone and wait for it to finish.
- Visit the staging URL (WP STAGING shows it at the end of the clone job). Log in to wp-admin using the same credentials as production.
- Confirm the noindex header is present: in the browser devtools Network tab, inspect a staging response and look for
X-Robots-Tag: noindex, nofollowor the noindex meta tag. WP STAGING adds the meta tag automatically. - Add an extra layer of protection on top of WP STAGING's default noindex. The meta tag alone is not enough (the reasons are in the hiding section below). At minimum add HTTP basic auth to the staging subfolder through
.htaccess. - Do your testing on staging.
- If you are on WP STAGING Pro, use Push Changes with selective sync to push specific tables and files back to production. If you are on Free, you will have to reapply the changes manually on production.
Method 3: manual clone on a subdomain
The manual method gives you the most control and works on any host, but it has the most steps and the most things to get wrong. Use this when your host does not offer staging, you do not want a staging plugin, and you are comfortable with WP-CLI or phpMyAdmin.
The high-level shape is: copy files, copy database, set up a subdomain, update wp-config.php, run a URL search-replace, add noindex and basic auth, done.
Step-by-step (subdomain staging on the same server):
- Create a subdomain. In your hosting panel (cPanel, Plesk, or equivalent), create
staging.example.comand point its document root at a new directory, for example/home/example/staging.example.com/. - Provision an SSL certificate for the subdomain. Most hosts issue a free Let's Encrypt certificate automatically when you create the subdomain. Verify it is active before moving on. Basic auth credentials should never travel over plain HTTP, which is why SSL comes before the clone.
- Copy the files. From the production document root:
cd /home/example/public_html/ rsync -av ./ /home/example/staging.example.com/ - Create a new database for staging. In cPanel or MySQL directly:
CREATE DATABASE example_staging; CREATE USER 'example_staging'@'localhost' IDENTIFIED BY 'strong-password'; GRANT ALL PRIVILEGES ON example_staging.* TO 'example_staging'@'localhost'; FLUSH PRIVILEGES; - Dump and import the database.
cd /home/example/public_html/ wp db export /tmp/production.sql cd /home/example/staging.example.com/ mysql -u example_staging -p example_staging < /tmp/production.sql - Update the staging
wp-config.php. Change the database constants to point at the staging database, and add the environment and URL constants. These override the values still stored in the database:
Important nuance from the WordPress handbook:// In /home/example/staging.example.com/wp-config.php define( 'DB_NAME', 'example_staging' ); define( 'DB_USER', 'example_staging' ); define( 'DB_PASSWORD', 'strong-password' ); define( 'DB_HOST', 'localhost' ); define( 'WP_HOME', 'https://staging.example.com' ); define( 'WP_SITEURL', 'https://staging.example.com' ); define( 'WP_ENVIRONMENT_TYPE', 'staging' ); define( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true ); define( 'WP_DEBUG_DISPLAY', false );WP_HOMEandWP_SITEURLdefined inwp-config.phpoverride the values inwp_optionsat runtime but do not rewrite them in the database. The staging database still contains the production URL in dozens of places: post content, menus, widgets, serialised plugin options. That is what the next step fixes. - Run the URL search-replace on the staging database. This is the step that catches people out. A raw SQL
UPDATEwill break serialised WordPress data. Use WP-CLI:
Thecd /home/example/staging.example.com/ # Dry-run first, always wp search-replace 'https://example.com' 'https://staging.example.com' \ --all-tables --precise --skip-columns=guid --dry-run # If the dry-run output looks right, run it for real wp search-replace 'https://example.com' 'https://staging.example.com' \ --all-tables --precise --skip-columns=guid--preciseflag forces PHP-based processing so serialised data is correctly handled.--skip-columns=guidis critical: the official WP-CLI search-replace docs explain that GUIDs are permanent post identifiers used by feed readers, and rewriting them causes duplicate-content alerts. The mechanics of this step and the Gutenberg JSON-escaped URL trap that can require a second pass are covered in full in the WordPress search-replace guide. - Load
https://staging.example.com/and verify the site renders, that internal links point atstaging.example.com, and that wp-admin is reachable. - Add noindex and basic auth per the dedicated section below. Do not skip this.
Expected output after step 7 (successful dry-run):
Success: 742 replacements to be made.
Expected output after step 8 (visiting staging):
The site loads at https://staging.example.com/, every internal link in the header menu points at staging.example.com, and the browser address bar does not redirect back to example.com. If it does redirect, WP_HOME or WP_SITEURL is not set or the URL replacement missed a table.
Method 4: local development tools as staging substitutes
Local tools like LocalWP, DevKinsta, Lando and raw Docker Compose create a WordPress environment on your laptop. They are excellent for building code and trying things out, but they are not a direct substitute for staging on the same infrastructure as production. The production server has a specific PHP version, a specific database engine, a specific object cache (or none), and usually a CDN and full-page cache in front of it. A local tool reproduces WordPress; it does not reproduce the production edge.
Use a local tool when:
- You are writing code (custom plugin, theme changes, block templates) and want a fast local edit-reload loop.
- You want to try a plugin configuration without committing to it anywhere.
- You are debugging a problem that reproduces locally.
Do not use a local tool when:
- You are doing the final pre-deploy check on a plugin update that might interact with your caching layer.
- You are testing performance or caching behaviour.
- You are validating a CDN or edge-specific configuration.
The tools:
- LocalWP (formerly Local by Flywheel) is the easiest entry point. Free, cross-platform, GUI-driven, with built-in SSL, MailHog for catching outgoing email, and one-click Live Link sharing. It integrates with Flywheel and WP Engine for push/pull, which turns it into a development-to-staging-to-production pipeline if you are hosted there.
- DevKinsta is Kinsta's equivalent, Docker-based, with one-click push/pull to Kinsta staging and production. Useful if you are on Kinsta; less so if you are not.
- Lando is an open-source Docker Compose wrapper that is not WordPress-specific. It supports WordPress, Drupal, Laravel and more through YAML recipes. It has a steeper learning curve but gives you more control and is team-friendly through a checked-in
.lando.yml. - Docker Compose without a wrapper gives maximum flexibility. You write your own
docker-compose.yml, manage volumes and networking, and get exactly the environment you define. This is the right choice for DevOps engineers who want staging as infrastructure-as-code.
A realistic workflow combines a local tool with a true staging environment: build code locally, push it to staging on the production host infrastructure, verify there, then promote to production. The local tool and the staging environment serve different purposes and both have a place.
Keeping staging hidden: noindex, X-Robots-Tag, and basic auth
The single biggest mistake people make with staging is relying on one protection layer. The WordPress reading setting "Discourage search engines from indexing this site" is a good starting point, but it is not sufficient on its own. Here is why each individual layer fails, and why the fix is to stack them.
Layer 1: the WordPress reading setting. Settings, Reading, "Discourage search engines from indexing this site". This adds <meta name="robots" content="noindex,nofollow"> to every page. Google respects it, but third-party crawlers and scrapers do not. Worse, the crawler still has to fetch the page to read the meta tag, which means your staging content is sitting in the memory of every crawler that visits.
Layer 2: robots.txt. Disallow: / for all user agents. The catch is that robots.txt is advisory only, and crucially, if a staging URL has already been discovered and indexed, adding a robots.txt disallow cannot remove it from the index. For removal from the index you need a noindex directive, which robots.txt is not.
Layer 3: X-Robots-Tag HTTP header. This is the server-level noindex directive. It is sent as an HTTP response header before any page content is read, it applies to every file type including PDFs and images, and it cannot be accidentally removed by a theme or plugin update. Yoast's X-Robots-Tag explainer walks through the mechanics in detail.
If your host's staging feature handles this automatically (Kinsta, WP Engine, and Pressable all do), verify it is present but do not add it manually. If you need to add it yourself, open the staging site's .htaccess file through your hosting panel's file manager (or via SFTP) and add the line below.
For Apache (.htaccess):
Header set X-Robots-Tag "noindex, nofollow"
For nginx (requires access to the server block config, typically via SSH):
add_header X-Robots-Tag "noindex, nofollow";
Layer 4: HTTP basic auth. This is the only layer that actually prevents crawlers and unauthenticated humans from reaching any content at all. Search engine crawlers cannot authenticate, so they are stopped at the front door and never see the page. This is why basic auth is the minimum baseline for a staging site that contains real customer data.
Most managed hosts have a "password protect this subdomain" or "directory privacy" toggle in the hosting control panel (cPanel, Plesk, DirectAdmin all have one). That toggle is basic auth under the hood. Use it when the option exists; it is the fastest and least error-prone path.
If you have SSH access and prefer to set it up manually, or your host does not have a panel toggle:
For Apache, create an .htpasswd file outside the document root and add this to the staging .htaccess:
AuthType Basic
AuthName "Staging - authorized access only"
AuthUserFile /home/example/.htpasswd
Require valid-user
Create the .htpasswd file with:
htpasswd -c /home/example/.htpasswd staging
For nginx, enable basic auth in the server block:
location / {
auth_basic "Staging - authorized access only";
auth_basic_user_file /home/example/.htpasswd;
try_files $uri $uri/ /index.php?$args;
}
The rule: X-Robots-Tag plus basic auth is the minimum. The WordPress reading setting is a nice-to-have but is not protection on its own. Basic auth on a non-HTTPS staging site sends credentials in the clear, so make sure SSL is active before you turn it on.
Keeping staging in sync with production
A staging site that was cloned three months ago is not a useful staging site. Plugins have moved on, content has changed, and the production database has drifted. You have three options for keeping staging reasonably fresh.
Option A: clone fresh when you need it. Delete the existing staging site, create a new clone from production, do your testing, delete the clone. This is the simplest workflow and works well for infrequent testing (quarterly plugin updates, occasional theme changes). The clone step is what the method sections above cover.
Option B: refresh on a schedule. Some managed hosts let you set a weekly or monthly refresh from production to staging. This keeps staging recent but means any in-progress testing on staging gets overwritten when the refresh runs. Use this only if you do not leave work-in-progress on staging.
Option C: manual selective refresh. Periodically pull just the production database into staging, leaving the staging files alone. This is useful when you are testing a code change that needs to see recent production data. WP STAGING Pro supports this; on managed hosts, the staging panel usually exposes an equivalent option.
Option A is the safest default. Option B is fine for sites where staging is a disposable test bench and nothing lives on it long-term. Option C is for teams doing active development against realistic data.
Pushing changes from staging to production safely
This is the part that breaks sites. A successful test on staging is not a one-click promote to live. The mental model from Pantheon's staging-to-production guide is the right one: code moves forward, data stays in production. Push themes, plugins, and custom code. Do not push the live database on top of production.
The safe workflow:
- Take a backup of production (files and database) right before the push. On-demand, not yesterday's scheduled backup. This is your undo button.
- Push files only from staging to production. This covers theme updates, plugin updates, custom code changes, new uploads directories, and any configuration that lives in files (like an updated
robots.txtor.htaccess). - If you must push database changes (new ACF field definitions, new plugin-settings rows, a menu change), sync only the specific tables that changed, never the full database. For WordPress, that usually means a subset of
wp_optionsandwp_postmeta, not the entire dump. - Never run a full database dump-restore from staging onto production. That is how you delete every order, comment, user account, form submission, and membership activity that happened between the clone and the push. It is not recoverable without a backup.
- After the push, verify the critical user flows on production. Log in, place a test order, submit a contact form, check that scheduled posts still run. The Pantheon guide explicitly names checkout, login and donation flows as the verification minimum.
Managed hosts like Kinsta and WP Engine implement this model through selective push tools. WP STAGING Pro implements it through its selective sync feature. If your setup does not have a selective push tool, the safe approach is: push files through rsync or git, and reapply database changes manually on production through the WordPress admin (re-enter the plugin setting, rebuild the menu) rather than copying database rows across.
What NOT to copy back: orders, users, live form data
The tables and data that must stay on production, regardless of method, are the ones that accumulate between the clone and the push. A non-exhaustive list:
- WooCommerce orders and customer data. With High-Performance Order Storage (HPOS) enabled, orders live in
wc_orders,wc_order_addresses,wc_order_items,wc_order_operational_dataand related tables. Before HPOS, orders lived insidewp_postsandwp_postmetawith ashop_orderpost type. Either way, these tables contain transactions that happened after your staging clone and must not be overwritten. WP STAGING Pro excludes WooCommerce order tables from push by default for this reason. - Comments.
wp_commentsandwp_commentmetaaccumulate between the clone and the push. - Users created after the clone.
wp_usersandwp_usermeta. Overwriting these deletes everyone who signed up after the clone date. - Form submissions. Plugins like Gravity Forms, Contact Form 7 Database, WPForms, and Ninja Forms store submissions in their own tables (
wp_gf_entry,wp_wpforms, etc.). These are silently overwritten by a full database push. - Membership or subscription state. MemberPress, Paid Memberships Pro, Restrict Content Pro all store per-user subscription state in custom tables that do not travel back to production.
- Payment gateway webhook state. Stripe, PayPal, Mollie and other gateways send webhooks back to production URLs. Staging should not be receiving production webhooks, and production webhook configuration should not be touched when pushing.
- Uploads added to production after the clone. The
wp-content/uploads/YYYY/MM/directories whereYYYY/MMis after the clone date. A file-level push must exclude these or it will delete uploads that production users added since.
The rule: if you did not change it in staging, do not push it from staging. The safest push is the smallest push. Pushing a single file is safer than pushing a directory; pushing a directory is safer than pushing the whole site; pushing files is safer than pushing the database; pushing a subset of tables is safer than pushing the whole database.
If you want a stronger safety net around this entire workflow, the WordPress backup strategy guide covers the backup rotation and retention patterns that let you recover when something slips through, and the WordPress security hardening guide covers the DISALLOW_FILE_EDIT and file-permission defaults that make staging and production both harder to mess up in the first place.