2026年11月13日

2026年11月13日

WordPress Staging Site: Plugin, WP-CLI and Safe Deploy to Production

This article is a localized edition of the Japanese original. Read the Japanese original

A staging site is a copy of your live site at a different URL where you try theme changes and plugin updates first. It keeps you from breaking the layout or taking the site down by experimenting on production.

The WP-CLI steps and PHP code in this article were tested on WordPress 7.1, WP-CLI 2.12 and PHP 8.3 as of November 2026.

Choosing a method

Method Best when Deploying to production
Your host's feature Your host offers staging or a site copy feature Depends on the host's feature
A plugin such as WP Staging Your host has no such feature and you want to work only in the admin The free version cannot push files or the database back
Manually with WP-CLI You have SSH and WP-CLI, for example on a VPS You define and run the deployment steps yourself
Local environment (wp-env and others) You develop themes or plugins Deploy to production with Git or similar

Start by checking your host's documentation for a staging or site copy feature. For example, Xserver, a Japanese hosting company, lets you copy a site from the WordPress installer in its server panel (described in its official manual on copying WordPress sites). The manual notes that URLs in some files may not be replaced, so check the copy after it is made.

Create one with the WP Staging plugin

WP Staging is a plugin that clones your live site into a subdirectory on the same server (for example, example.com/staging-site). As of November 2026 it has more than 100,000 active installations.

  1. Go to Plugins → Add Plugin, search for "WP Staging", then install and activate it.
  2. Open the new WP Staging menu in the admin and create a staging site.
  3. When it finishes, you can log in to the staging site's admin at the subdirectory URL.

The free version only creates the clone. Pushing staging changes back to production and cloning to a subdomain are features of the paid Pro version. If you use the free version, deploy the changes you tested using the steps later in this article.

Create one manually with WP-CLI

This assumes a VPS or similar where a staging subdomain (staging.example.com here) and its document root (/var/www/staging) are already configured in the web server. Production lives in /var/www/html, and the database is MariaDB (MySQL works the same way).

1. Copy the files

rsync -a --exclude='wp-content/cache/' /var/www/html/ /var/www/staging/

2. Create a staging database and load the production data

# Export the production database (keep it outside the document root)
wp db export ~/production.sql --path=/var/www/html

# Create the staging database and user
sudo mysql -e "CREATE DATABASE wp_staging DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_staging'@'localhost' IDENTIFIED BY 'a-long-hard-to-guess-password';
GRANT ALL PRIVILEGES ON wp_staging.* TO 'wp_staging'@'localhost';"

3. Edit the staging wp-config.php and import the data

cd /var/www/staging
wp config set DB_NAME wp_staging
wp config set DB_USER wp_staging
wp config set DB_PASSWORD 'a-long-hard-to-guess-password'
wp config set WP_ENVIRONMENT_TYPE staging

wp db import ~/production.sql
rm ~/production.sql

Because wp config set changes the settings before wp db import runs, the data goes into the staging database, not production. Checking with wp config get DB_NAME first gives you peace of mind.

4. Replace the URLs

Replace the production URL in the database with the staging URL. wp search-replace also handles serialized data correctly. Check the number of changes with --dry-run first, then run it.

wp search-replace 'https://example.com' 'https://staging.example.com' \
  --all-tables-with-prefix --skip-columns=guid --dry-run --report-changed-only

wp search-replace 'https://example.com' 'https://staging.example.com' \
  --all-tables-with-prefix --skip-columns=guid --report-changed-only

wp option get siteurl
  • --all-tables-with-prefix limits the run to tables with the WordPress prefix (such as wp_), including tables created by plugins.
  • --skip-columns=guid leaves the post identifiers (GUIDs) unchanged.

5. Keep search engines out

wp option update blog_public 0
wp cache flush

Setting blog_public to 0 is the same as checking "Discourage search engines from indexing this site" under "Search engine visibility" in Settings → Reading. Every page then gets a noindex, nofollow robots meta tag. In our test, this setting did not add "Disallow: /" to robots.txt. Since the meta tag is the only signal to search engines, also restrict access as described in the next section.

Restrict access

A staging site has the same content as production, so if outsiders can see it you risk duplicate content and exposing unreleased changes. Use HTTP Basic authentication so only people with the credentials can open it.

On shared hosting, the access restriction feature in the control panel is the easiest route. If you manage Apache 2.4 yourself, create a password file and add the following at the top of the .htaccess in the staging document root (before # BEGIN WordPress).

# Create the password file outside the document root
sudo htpasswd -c /etc/apache2/.htpasswd-staging staginguser
AuthType Basic
AuthName "Staging"
AuthUserFile /etc/apache2/.htpasswd-staging
Require valid-user

Basic authentication does not encrypt traffic, so serve the staging site over HTTPS too. For protecting the login screen, see also login settings for a staging site.

Tell environments apart with WP_ENVIRONMENT_TYPE

WP_ENVIRONMENT_TYPE is a constant that tells WordPress which environment the site is. The value is one of local, development, staging or production; any other value, or no value, is treated as production. Plugins and themes read it with wp_get_environment_type().

<?php
// wp-config.php (place before require_once ABSPATH . 'wp-settings.php';)
define( 'WP_ENVIRONMENT_TYPE', 'staging' );

// Outside production, log errors but do not display them
if ( 'production' !== WP_ENVIRONMENT_TYPE ) {
	define( 'WP_DEBUG', true );
	define( 'WP_DEBUG_LOG', true );
	define( 'WP_DEBUG_DISPLAY', false );
}

wp_get_environment_type() is only available after wp-settings.php has loaded. You cannot call it inside wp-config.php, so compare the constant directly as shown above.

If you do not define WP_DEBUG, WordPress sets it to true only when WP_ENVIRONMENT_TYPE is development. With staging it stays false (our test confirmed this).

Add noindex and an environment label outside production

Save the following as wp-content/mu-plugins/env-helpers.php. Outside production, every page gets noindex and the admin bar shows the environment name. Files in mu-plugins load without being activated.

<?php
/**
 * Plugin Name: Environment helpers
 * Description: Outside production, add noindex and show the environment name in the admin bar.
 */

// Outside production, add noindex to the robots meta tag on every page
add_filter( 'wp_robots', function ( $robots ) {
	if ( 'production' !== wp_get_environment_type() ) {
		$robots = wp_robots_no_robots( $robots );
	}
	return $robots;
} );

// Outside production, show the environment name (STAGING, etc.) in the admin bar
add_action( 'admin_bar_menu', function ( $wp_admin_bar ) {
	$env = wp_get_environment_type();
	if ( 'production' === $env ) {
		return;
	}
	$wp_admin_bar->add_node(
		array(
			'id'    => 'myprefix-environment',
			'title' => esc_html( strtoupper( $env ) ),
		)
	);
}, 100 );

In our test, setting WP_ENVIRONMENT_TYPE to staging added noindex, follow to the robots meta tag (noindex, nofollow when "Search engine visibility" is checked), and removing the constant removed it. The same file does nothing on production.

Deploy from staging to production

After you create the staging site, production keeps gaining posts, comments, orders and more. Overwriting production with the staging database wipes out those changes. As a rule, deploy files (themes and plugins) and do not push the database back.

1. Back up production

mkdir -p ~/backup
cd /var/www/html
wp db export ~/backup/pre-deploy-$(date +%Y%m%d%H%M).sql
tar -czf ~/backup/wp-content-$(date +%Y%m%d%H%M).tar.gz wp-content/themes wp-content/plugins

Keep backups outside the document root. Inside it, anyone who learns the URL could download them.

2. Sync only the theme you changed

# First check which files will be added or deleted with --dry-run
rsync -av --delete --dry-run \
  /var/www/staging/wp-content/themes/my-theme/ \
  /var/www/html/wp-content/themes/my-theme/

# If it looks right, run it
rsync -av --delete \
  /var/www/staging/wp-content/themes/my-theme/ \
  /var/www/html/wp-content/themes/my-theme/

cd /var/www/html
wp cache flush

--delete removes files from the destination that are not in the source. If you forget the trailing / on a path, you end up with an extra directory level, so check the --dry-run output before running it.

3. Repeat plugin updates on production

Once a plugin or theme update works on staging, run the same update on production. Unlike copying files, this also runs any database upgrade routines on production the same way.

cd /var/www/html
wp plugin update akismet   # Update only what you tested on staging

4. Redo settings changes on production

For changes stored in the database, such as widgets, menus and plugin settings, repeat the steps you verified on staging in the production admin.

Set up a local development environment (wp-env)

If you develop themes or plugins, you can also run WordPress on your own computer. @wordpress/env (wp-env) is the official WordPress development tool and requires Docker and Node.js.

npm -g i @wordpress/env

# Start it in your plugin or theme directory
wp-env start

WordPress then runs at http://localhost:8888, and you can log in with the username admin and the password password. A separate test site runs at http://localhost:8889. Change the setup with a .wp-env.json file in the directory.

{
  "core": null,
  "phpVersion": "8.3",
  "plugins": [ "." ],
  "themes": [],
  "port": 8888,
  "config": {
    "WP_DEBUG": true
  }
}

With core set to null, the latest WordPress release is used. The "." in plugins loads the current directory as a plugin.

If you prefer a GUI, there are desktop apps such as Local (localwp.com). For more on local environments, see how to set up a local WordPress development environment.

Things to watch on staging

  • Outgoing email: Staging holds the same users and orders as production. Scheduled posts, recurring jobs (WP-Cron) or contact form tests can send email to real users. Turn off outgoing mail before you start testing.
  • External services: If payment or API keys are still the live ones, actions on staging can become real transactions. Switch to test keys.
  • Personal data: Staging also contains your users' personal data. Delete the files and database of staging sites you no longer need.

Related articles

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲