# How to create a WordPress staging site on your own VPS

> Set up a WordPress staging site on a VPS with a panel or by hand with wp-cli, keep it out of Google, and push changes live without losing orders.

Source: https://zopanel.net/blog/wordpress-staging  
Updated: 2026-10-21

## Key takeaways

- A WordPress staging site is a full copy of files and database on another domain, used to test changes.
- A staging copy must be blocked from search engines, real emails and live payment gateways.
- Pushing staging to live overwrites the database, so new orders and comments on the live site can be lost.
- For WooCommerce stores, test on staging and repeat the changes on live instead of pushing the database.

A WordPress staging site is a complete copy of your live site (files and database) running on a separate address such as `staging.example.com`, where you test updates and changes before visitors see them. To create one, you copy the files and database, replace the URLs inside the database, then block search engines and lock the copy behind a password. With a control panel that supports it this takes a few minutes; by hand with wp-cli, about half an hour.

This guide covers when staging is worth it, what a safe staging copy needs, how to create one with ZoPanel and manually on Ubuntu or Debian, and the step where most people lose data: getting changes back to the live site.

## What is a WordPress staging site, and when do you need one?

Staging is a test environment that matches production: same WordPress version, plugins, theme and data. The only difference is the address, which your visitors never see.

You need it whenever a change could break the site:

- **Major updates**: a new WordPress major version, WooCommerce, page builders, membership plugins.
- **Theme changes or upgrades**, especially with a child theme or custom CSS.
- **PHP upgrades**, for example from 8.1 to 8.3. Older plugins often fail here.
- **New site-wide plugins**: caching, security, SEO, multilingual.
- **Code changes** in `functions.php` or a custom plugin.

For a personal blog, a backup before updating may be enough. For a shop, a client site you are paid to keep running, or anything where an hour of downtime costs money, staging is close to mandatory.

## What does a safe staging copy need?

A staging site is a copy of real data, so if you leave it open it can cause harm in ways that are easy to miss. Check this list before you start.

| Task | Why |
| --- | --- |
| Block search engines | If Google indexes staging, you get duplicate content competing with the live site. |
| Lock access (HTTP password or IP allowlist) | Staging holds real customer data. Strangers should not browse it. |
| Block or redirect outgoing email | Order confirmations and newsletters should not reach real customers from a test copy. |
| Switch payment gateways to test mode | No real transactions from staging. |
| Turn off outbound integrations (stock sync, CRM, webhooks) | Staging must not write to real systems. |
| Mark the environment | Set `WP_ENVIRONMENT_TYPE` to `staging` so plugins know this is not production. |

`WP_ENVIRONMENT_TYPE` was added in WordPress 5.5 and accepts `local`, `development`, `staging` and `production`; anything else is treated as `production` ([WordPress reference](https://developer.wordpress.org/reference/functions/wp_get_environment_type/)). Not every plugin reads it, so do not rely on it alone to stop emails or payments.

A note on "blocking search engines": WordPress's **Search engine visibility** option only adds a `noindex` tag and changes `robots.txt`. It is a request, not a lock. An HTTP password is what actually keeps bots and people out.

## How to create a WordPress staging site with ZoPanel

In ZoPanel, staging lives on a website's **WordPress** tab. The tab reads the installation with wp-cli, so it also works for sites you uploaded or migrated from another host.

### Step 1: Create the staging copy

1. Open the website, go to the **WordPress** tab and click **Create staging**.
2. The domain defaults to `staging.<your domain>`. Any domain or subdomain of the account works.
3. ZoPanel copies the files and database to a new website, replaces the URLs and hides the copy from search engines.

### Step 2: Point DNS and wait for SSL

Create an A record for `staging.example.com` pointing to the server's IP. Once it resolves, the SSL certificate is issued automatically. If DNS is new to you, see [how to point a domain to a VPS](/blog/point-domain-to-vps).

### Step 3: Work on staging

The staging site's WordPress tab shows **staging of example.com**, so you cannot confuse the two. All the usual tools work: **Log in to WordPress** with a one-time link, plugin updates, switching the PHP version on the **PHP & config** tab, and **Debug logging** in the **Maintenance** card to collect errors in `wp-content/debug.log`.

To put the whole staging site behind a password, use **Password-protected directories** on the **Tools** tab with the path `/`.

### Step 4: Publish to live

When you are happy, open the staging site's WordPress tab and click **Publish to live**. ZoPanel:

- replaces the live site's files with the staging ones, except `wp-config.php`, `.user.ini` and `.maintenance`;
- replaces the live database with the staging one, then swaps the staging URL for the live URL throughout the database with a wp-cli search-replace;
- keeps the live site's search-engine visibility setting and flushes caches;
- **takes a snapshot of the live site first**, and restores it automatically if the live site breaks after publishing.

Staging copies count toward the hosting package's website and database limits. On the Free plan (3 hosting accounts, 10 websites, 10 databases), each staging copy uses one of each. Details are in the [WordPress Toolkit docs](/docs/wordpress).

## How to create a staging site manually on a VPS

If you run the server by hand, this procedure works on Ubuntu 24.04 and Debian 12 with nginx, PHP-FPM, MariaDB and [wp-cli](https://wp-cli.org/). The examples assume the live site is in `/var/www/example.com` and owned by `www-data`. Adjust paths, user and domain to your server.

### Step 1: Copy the files

```bash
sudo rsync -a --exclude 'wp-content/cache/' \
  /var/www/example.com/ /var/www/staging.example.com/
```

Skip the cache folder: it holds rendered pages with the old URLs.

### Step 2: Create a separate database

```bash
sudo mariadb -e "CREATE DATABASE wp_staging;
CREATE USER 'wp_staging'@'localhost' IDENTIFIED BY 'change-to-a-strong-password';
GRANT ALL PRIVILEGES ON wp_staging.* TO 'wp_staging'@'localhost';"
```

Use a separate database user. If staging code misbehaves, it cannot write to the live database by mistake.

### Step 3: Edit the staging wp-config.php

```bash
cd /var/www/staging.example.com
sudo -u www-data wp config set DB_NAME wp_staging
sudo -u www-data wp config set DB_USER wp_staging
sudo -u www-data wp config set DB_PASSWORD 'change-to-a-strong-password'
sudo -u www-data wp config set WP_ENVIRONMENT_TYPE staging
```

Do this **before** importing the database. Otherwise the next wp-cli command connects to the live database.

### Step 4: Copy the database and replace URLs

```bash
sudo -u www-data wp --path=/var/www/example.com db export /tmp/live.sql
sudo -u www-data wp db import /tmp/live.sql
sudo rm /tmp/live.sql

sudo -u www-data wp search-replace 'https://example.com' \
  'https://staging.example.com' --all-tables-with-prefix \
  --skip-columns=guid --dry-run
```

Review the dry run, then run it again without `--dry-run`. Do not change URLs with a plain SQL `UPDATE`: WordPress stores a lot of serialized data, and `wp search-replace` handles it correctly ([command reference](https://developer.wordpress.org/cli/commands/search-replace/)). The `guid` column is skipped because post GUIDs should never change.

Then block search engines and flush the cache:

```bash
sudo -u www-data wp option update blog_public 0
sudo -u www-data wp cache flush
```

### Step 5: nginx, SSL and a password

Copy the live site's server block, change `server_name` and `root`, then run `sudo nginx -t && sudo systemctl reload nginx`. Issue a certificate with Certbot as you did for the live site. Then add an HTTP password:

```bash
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-staging dev
```

```nginx
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd-staging;

location /.well-known/acme-challenge/ {
    auth_basic off;
}
```

The last `location` block lets Let's Encrypt keep renewing the certificate behind the password. Put it at the same level as the other `location` blocks in the server block.

## Pushing staging to live: where data gets lost

Creating staging is easy. Getting changes back is where people lose data. The problem is the database: while you work on staging, the live site keeps running. Customers place orders, leave comments and register. If you overwrite the live database with the staging one, everything that happened since the copy disappears.

| Type of site | Safe way back to live |
| --- | --- |
| Blog or brochure site with rare updates | Push files and database, as long as nobody published on live while you worked. |
| Site with comments or forms stored in the database | Freeze posting and comments, or re-create staging right before you push. |
| WooCommerce, membership, courses | Do not push the database. Test on staging, then repeat the same steps on live. |

For a shop, the practical workflow is: test updates on staging, note which plugins and versions work, then apply exactly those updates on the live site. In ZoPanel, **Update all** on the live site runs a safe update: it snapshots files and database, updates, checks `/` and `/wp-login.php`, and rolls back automatically if the site broke. Staging tells you in advance whether an update is fine; the safe update is a second safety net for the ways live still differs from staging.

If you push by hand, keep this order: back up live (files and database), enable maintenance mode, push files with `rsync`, export the staging database, import it on live, run `wp search-replace` in the opposite direction, set `blog_public` back to 1, flush caches, and turn maintenance mode off. Forgetting the URL replacement is the most common mistake: the live site then redirects visitors to the staging domain.

## Common staging mistakes

- **Forgetting `blog_public` after a manual push.** The live site goes `noindex` and slides out of Google over a few weeks. Check **Settings → Reading** right after pushing.
- **Staging emails real customers.** WooCommerce, form plugins and newsletter plugins all send mail. Point SMTP on staging at a test mailbox or use a plugin that blocks outgoing email.
- **Scheduled jobs running twice.** Abandoned-cart reminders and stock syncs will run on staging too. Disable those integrations there.
- **Forgotten staging copies.** A staging site left for months is an outdated WordPress install, full of real data, open on the internet. Delete it when you are done.
- **Staging that does not match live.** If staging runs PHP 8.3 and live runs 8.1, your test means little. Keep PHP versions and caching the same.
- **Page cache and CDN.** After pushing, visitors may see old pages until caches expire. Purge both the server cache and the CDN.

## Does a staging site slow the server down?

A staging copy is a full WordPress site, so it uses as much disk as the live site plus one more database. CPU and RAM cost is close to zero while nobody uses it. On ZoPanel, a site's PHP pool sleeps after its idle time (30 minutes by default), so an idle staging copy holds no PHP memory. We measured how this affects density in [how many WordPress sites a 4 GB VPS can host](/blog/how-many-wordpress-sites-4gb-vps).

## Conclusion

Staging is not complicated: a full copy, URLs replaced properly, locked away from Google and strangers, with no real emails or transactions. The part that needs care is the way back: on sites that take orders or registrations all day, never overwrite the live database.

If you would rather do this in a few clicks, ZoPanel has **Create staging**, **Publish to live** with an automatic rollback snapshot, and safe updates for the live site. Staging and safe updates are included on the Free plan (3 hosting accounts, 10 websites, 10 databases, no time limit); see [pricing](/pricing) and the [backups guide](/docs/backups) to get started.

## Frequently asked questions

### Will Google index my WordPress staging site?

It can, if you leave it open. WordPress's search-engine visibility setting only adds a `noindex` tag, and bots can still crawl the site. The reliable fix is an HTTP password on the staging site, and deleting it when you no longer need it.

### Do I need a plugin to create a WordPress staging site?

No. Plenty of plugins do it, but on a VPS you can build staging with wp-cli and nginx as shown above, or use your control panel's staging feature. Server-level copies are usually more reliable for large sites because they are not limited by PHP's web execution time and upload size.

### Will pushing staging to live delete WooCommerce orders?

Yes, if you push the database. The staging database does not contain orders placed on live after staging was created. For a shop, test on staging and repeat the changes on live, or pause sales while you push.

### Should staging be a subdomain or a subfolder?

Use a subdomain such as `staging.example.com`. A subfolder shares cookies, `.htaccess` rules and cache rules with the live site, which causes conflicts. A subdomain is fully separate and gets its own certificate.

### Does a staging copy count toward my hosting limits?

In ZoPanel, yes: each staging copy or clone counts as one website and one database in the package. Delete staging when you are done to free the slot.
