Back to tutorials
Tutorial

WordPress staging site tutorial (2026): Create a safe clone on your VPS, test updates, and push changes

WordPress staging site tutorial for 2026: clone your site on a VPS, test updates safely, then push changes with minimal risk.

By Anurag Singh
Updated on Jun 28, 2026
Category: Tutorial
Share article
WordPress staging site tutorial (2026): Create a safe clone on your VPS, test updates, and push changes

Production WordPress fails in predictable, annoying ways. A plugin update tweaks rewrite rules. A theme update overwrites a customization. A PHP bump triggers a fatal error.

A staging copy removes the guesswork. This WordPress staging site tutorial shows you how to clone a live site onto your VPS, keep it private (and quiet), and push the right changes back without taking production down.

The steps assume an Ubuntu 24.04/26.04-class server with Nginx or Apache and PHP-FPM.

If you want a server you can grow into, start on a HostMyCode VPS. If you’d rather not maintain the OS, managed VPS hosting is usually the cleaner fit for businesses in 2026.

What you’ll build (and what you won’t)

You’ll create staging.yourdomain.com as a private clone of your live site:

  • Separate vhost + separate webroot
  • Separate database (so tests don’t touch production)
  • Basic auth or IP allowlist to keep it private
  • Robots/noindex plus hard blocks for common crawlers
  • Email suppression so staging can’t spam customers
  • A repeatable “pull from production” + “push to production” process

You won’t build a full Git-based deployment pipeline here. Git can be great. You just don’t need it to ship safer WordPress updates.

Prerequisites checklist (do this before cloning)

  • DNS: You can create an A/AAAA record for staging.
  • Disk space: You have enough room for a second copy of wp-content and a second database dump.
  • SSH access: Root or sudo on the server hosting WordPress.
  • Backups: A recent offsite backup exists and you’ve done at least one restore test.

If your backup posture is “we have a plugin,” stop here.

Set up a real server-side backup plan. Then run a restore drill before you clone anything.

Two relevant guides: incremental backups with Restic + S3 and a full VPS restore drill.

Step 1: Create DNS and decide on access control

Create a DNS record:

  • A record: staging → your server IPv4
  • AAAA record (optional): staging → your server IPv6

Set TTL to 300 seconds while you build and test.

If you want a safer workflow for future cutovers, bookmark this: DNS propagation planning with rollback.

Now choose how you’ll keep staging private:

  • Best default: HTTP Basic Auth (works anywhere)
  • Good for teams on known networks: IP allowlist
  • For client-facing staging: Basic Auth plus a shared password

Step 2: Create a staging webroot and copy WordPress files

On your VPS, find the production document root. Common paths include:

  • /var/www/yourdomain.com/public
  • /var/www/html
  • /home/username/public_html (shared hosting style)

Example layout we’ll use:

  • Production: /var/www/yourdomain.com/public
  • Staging: /var/www/staging.yourdomain.com/public
sudo mkdir -p /var/www/staging.yourdomain.com/public
sudo rsync -a --delete \
  /var/www/yourdomain.com/public/ \
  /var/www/staging.yourdomain.com/public/

Important: You usually don’t want to copy caches.

If you have cache directories (varies by plugin), exclude them:

sudo rsync -a --delete \
  --exclude 'wp-content/cache/' \
  --exclude 'wp-content/uploads/cache/' \
  /var/www/yourdomain.com/public/ \
  /var/www/staging.yourdomain.com/public/

Set ownership to your web user (commonly www-data on Ubuntu):

sudo chown -R www-data:www-data /var/www/staging.yourdomain.com/public

Step 3: Clone the database into a separate staging database

Staging needs its own database. Otherwise, one “test” action can become a real production change.

Start by pulling production DB details from wp-config.php:

grep -E "DB_NAME|DB_USER|DB_HOST" /var/www/yourdomain.com/public/wp-config.php

Create a staging database and user. Replace values as needed:

sudo mysql
CREATE DATABASE wp_staging DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_staging_user'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT ALL PRIVILEGES ON wp_staging.* TO 'wp_staging_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Dump production and import into staging:

mysqldump --single-transaction --quick --routines --triggers \
  -u PROD_DB_USER -p PROD_DB_NAME > /root/prod.sql

mysql -u wp_staging_user -p wp_staging < /root/prod.sql

If the dump is large, compress it while streaming:

mysqldump --single-transaction --quick \
  -u PROD_DB_USER -p PROD_DB_NAME | gzip > /root/prod.sql.gz

gunzip -c /root/prod.sql.gz | mysql -u wp_staging_user -p wp_staging

Step 4: Point staging WordPress at the staging database

Edit staging wp-config.php:

sudo nano /var/www/staging.yourdomain.com/public/wp-config.php

Update at least:

  • DB_NAMEwp_staging
  • DB_USERwp_staging_user
  • DB_PASSWORD → your new password

While you’re in there, add a simple staging marker.

It makes staging easier to spot during testing:

define('WP_ENVIRONMENT_TYPE', 'staging');

Step 5: Update site URLs inside the staging database (search/replace)

Your database stores absolute URLs in home and siteurl. It also stores URLs inside content and plugin options.

Replace the production domain with the staging subdomain using a WordPress-aware tool. That avoids breaking serialized data.

If you have WP-CLI, use it. From the staging webroot:

cd /var/www/staging.yourdomain.com/public

sudo -u www-data wp option get home
sudo -u www-data wp option get siteurl

Run a dry-run first. Confirm what will change:

sudo -u www-data wp search-replace 'https://yourdomain.com' 'https://staging.yourdomain.com' --dry-run

Then run the real replacement:

sudo -u www-data wp search-replace 'https://yourdomain.com' 'https://staging.yourdomain.com' --all-tables

If production uses http internally but redirects to https, run the http variant too:

sudo -u www-data wp search-replace 'http://yourdomain.com' 'https://staging.yourdomain.com' --all-tables

Step 6: Configure the web server vhost for staging (Nginx or Apache)

Use a dedicated vhost for staging. Keep its document root, logs, and TLS separate from production.

Nginx example (Ubuntu)

Create /etc/nginx/sites-available/staging.yourdomain.com:

server {
  listen 80;
  server_name staging.yourdomain.com;

  root /var/www/staging.yourdomain.com/public;
  index index.php index.html;

  access_log /var/log/nginx/staging.access.log;
  error_log  /var/log/nginx/staging.error.log;

  location / {
    try_files $uri $uri/ /index.php?$args;
  }

  location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
  }

  location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp)$ {
    expires 7d;
    add_header Cache-Control "public";
  }
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/staging.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Apache example (Ubuntu)

Create /etc/apache2/sites-available/staging.yourdomain.com.conf:

<VirtualHost *:80>
  ServerName staging.yourdomain.com
  DocumentRoot /var/www/staging.yourdomain.com/public

  ErrorLog ${APACHE_LOG_DIR}/staging-error.log
  CustomLog ${APACHE_LOG_DIR}/staging-access.log combined

  <Directory /var/www/staging.yourdomain.com/public>
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>

Enable and reload:

sudo a2ensite staging.yourdomain.com
sudo apachectl configtest
sudo systemctl reload apache2

Step 7: Add HTTPS and fix common Let’s Encrypt failures

Issue a certificate for the staging subdomain. On Ubuntu in 2026, Certbot remains the simplest path.

Nginx:

sudo apt update
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d staging.yourdomain.com

Apache:

sudo apt update
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d staging.yourdomain.com

If issuance fails, don’t “try random things.”

The usual culprits are DNS pointing elsewhere, port 80 blocked, or the wrong vhost matching the request. This guide walks through the common fixes: fix Let’s Encrypt renewal/issuance failures.

Step 8: Keep staging private (Basic Auth + robots + hard blocks)

Staging should not show up in search results. It also should not be an easy target for brute-force logins.

Treat it like an internal tool, even if it’s publicly reachable.

Option A: HTTP Basic Auth (Nginx)

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-staging staginguser

Add to your staging server block:

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

Reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Option B: HTTP Basic Auth (Apache)

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/apache2/.htpasswd-staging staginguser

In the vhost:

<Directory /var/www/staging.yourdomain.com/public>
  AuthType Basic
  AuthName "Staging"
  AuthUserFile /etc/apache2/.htpasswd-staging
  Require valid-user
</Directory>

Reload Apache:

sudo apachectl configtest
sudo systemctl reload apache2

Robots and noindex

Create a staging-only robots.txt:

cat > /var/www/staging.yourdomain.com/public/robots.txt <<'EOF'
User-agent: *
Disallow: /
EOF

Also set a noindex header at the web server layer.

This helps when a crawler ignores robots.txt or never fetches it first.

Nginx inside the staging server block:

add_header X-Robots-Tag "noindex, nofollow, noarchive" always;

Apache inside the vhost:

Header always set X-Robots-Tag "noindex, nofollow, noarchive"

If you don’t already have headers enabled on Apache:

sudo a2enmod headers
sudo systemctl reload apache2

Step 9: Stop staging from sending real emails (critical)

Test emails are fine. Emailing real customers from staging is not.

Put a hard block in place so mistakes don’t turn into incidents.

Simple approach: route all WordPress mail to a sink address

Install a mail logging/sink plugin. Or use a must-use plugin so it can’t be disabled in the UI.

Create:

sudo mkdir -p /var/www/staging.yourdomain.com/public/wp-content/mu-plugins
sudo nano /var/www/staging.yourdomain.com/public/wp-content/mu-plugins/staging-mail-sink.php

Add:

<?php
/**
 * Plugin Name: Staging Mail Sink
 */
add_filter('wp_mail', function($args){
  $args['to'] = 'staging-inbox@yourdomain.com';
  $args['subject'] = '[STAGING] ' . $args['subject'];
  return $args;
});

This keeps mail flows testable (templates, hooks, attachments). It also forces all mail into a controlled inbox.

If you run email services on the same VPS, keep authentication correct. Otherwise, you can create deliverability problems later.

These two guides are solid references: SPF/DKIM/DMARC + rDNS setup and deliverability troubleshooting.

Step 10: Fix WordPress cron behavior for staging tests

Staging often has low traffic. That means wp-cron.php runs less often.

Scheduled tasks can look “broken” simply because nobody visited the site.

For realistic testing, switch to a real system cron.

Disable pseudo-cron in staging wp-config.php:

define('DISABLE_WP_CRON', true);

Add a cron job (as root) every 5 minutes:

sudo crontab -e
*/5 * * * * sudo -u www-data /usr/bin/php -d detect_unicode=0 /var/www/staging.yourdomain.com/public/wp-cron.php >/dev/null 2>&1

If scheduled posts still miss, this guide stays focused on the usual causes: WordPress cron troubleshooting.

Step 11: “Pull” workflow — refresh staging from production safely

Staging drifts over time. Settings change. Plugins come and go. Content keeps moving.

A controlled refresh keeps your tests honest.

  1. Put staging in maintenance mode (optional but tidy). You can do this via a plugin, or just warn your team.
  2. Resync files (exclude caches):
sudo rsync -a --delete \
  --exclude 'wp-content/cache/' \
  /var/www/yourdomain.com/public/ \
  /var/www/staging.yourdomain.com/public/
  1. Dump production DB and import into staging (same commands as earlier).
  2. Run WP-CLI search/replace for URLs again.
  3. Re-apply staging-only settings: mail sink MU-plugin, noindex header, basic auth.

Pitfall: If you keep environment-specific values in wp-config.php (API keys, payment gateways), don’t overwrite staging’s config during rsync.

Exclude it:

sudo rsync -a --delete \
  --exclude 'wp-config.php' \
  --exclude 'wp-content/cache/' \
  /var/www/yourdomain.com/public/ \
  /var/www/staging.yourdomain.com/public/

Step 12: “Push” workflow — move changes from staging to production

Pushing is where people get burned. First, define what the “change” actually is.

In most WordPress workflows, the safest default is pushing code and configuration, not content.

What you should usually push

  • Theme files (prefer child theme changes)
  • Plugin updates (the plugin code)
  • Configuration that lives in files: wp-config.php flags, Nginx/Apache config, PHP-FPM pool settings

What you should not push blindly

  • The whole database (you’ll overwrite real orders, form submissions, and user changes)
  • wp-content/uploads unless you know exactly what changed

Push plugin/theme updates using rsync (code only)

If you updated plugins on staging, push only wp-content/plugins (and/or mu-plugins and themes).

Example:

# Push plugins (careful: this overwrites production plugin code)
sudo rsync -a --delete \
  /var/www/staging.yourdomain.com/public/wp-content/plugins/ \
  /var/www/yourdomain.com/public/wp-content/plugins/

# Push themes
sudo rsync -a --delete \
  /var/www/staging.yourdomain.com/public/wp-content/themes/ \
  /var/www/yourdomain.com/public/wp-content/themes/

Then clear caches (varies by stack). If you run PHP-FPM, a reload clears opcode cache:

sudo systemctl reload php8.3-fpm

For a busy store, schedule this for a quiet window. Verify key flows right after (checkout, login, contact forms).

If you need help planning a low-downtime move or maintenance window on new infrastructure, HostMyCode migrations can handle the cutover steps and verification.

Push specific database changes (only when needed)

Sometimes you do need DB changes. Common examples are a settings toggle, a new plugin option, or a redirect rule stored in the DB.

Even then, avoid exporting/importing the full database.

Two safer patterns:

  • Reproduce the change manually in production while you have a checklist. Slower, but you keep control.
  • Export a narrow set of tables if you’re confident which tables a plugin uses (still risky if they contain live data).

If you choose the narrow export route, start by identifying which tables changed on staging.

If you have binary logs and auditing, use them. If not, compare row counts before/after. Also confirm the tables don’t hold customer data.

Step 13: Performance sanity checks on staging (so you don’t ship surprises)

Staging is where you catch slow pages before customers feel them. A quick triage usually finds the obvious problems.

  • PHP errors: scan logs for fatals and repeated warnings
  • Time to first byte: compare before/after a plugin update
  • Disk and CPU spikes: watch during imports, indexing, or crawler runs

Useful commands:

# Nginx errors
sudo tail -n 200 /var/log/nginx/staging.error.log

# Apache errors
sudo tail -n 200 /var/log/apache2/staging-error.log

# PHP-FPM status (if enabled) and system load
uptime
free -h
df -h

If you’re tuning PHP for WordPress, don’t guess.

This guide covers practical pool sizing and settings that hold up under load: PHP-FPM performance tuning for WordPress.

Step 14: Security guardrails you can apply in 15 minutes

Staging sites get forgotten. Attackers don’t forget them.

Add a few guardrails to avoid the usual mess.

  • Keep staging behind auth (Basic Auth or IP allowlist).
  • Disable XML-RPC on staging unless you need it.
  • Limit admin accounts and force strong passwords.
  • Patch the OS and PHP regularly.

If staging and production share a VPS, harden the box once. Then keep it maintained.

Two practical starting points: hardening a new Ubuntu VPS for hosting and automated maintenance routines.

Step 15: A repeatable staging checklist (print this)

  • DNS: staging record points correctly, TTL sensible.
  • Vhost: separate logs, correct docroot, correct PHP-FPM socket/version.
  • TLS: Let’s Encrypt cert issued and auto-renewing.
  • Privacy: Basic Auth/IP allowlist, X-Robots-Tag, robots.txt.
  • Email: mail sink enabled, subject prefixed with [STAGING].
  • Database: separate DB/user, URLs replaced with WP-CLI.
  • Cron: real cron enabled for realistic scheduled tasks.
  • Push plan: code-only push by default; DB changes only with explicit scope.
  • Backups: staging included or explicitly excluded (but never breaks production backups).

If you run staging and production on the same box, you’ll hit limits fast: disk, RAM, and test loads that compete with real traffic. A HostMyCode VPS gives you predictable resources for safe staging workflows, and managed VPS hosting is the easiest way to keep WordPress, PHP, and TLS maintained in 2026.

FAQ

Should staging live on the same VPS as production?

For small sites, yes—if you isolate vhosts and databases and keep staging private. For busy stores, a separate staging box prevents tests from stealing CPU and I/O from paying customers.

Do I need a separate database user for staging?

Yes. It limits blast radius and makes it obvious which credentials belong to staging. It also helps prevent scripts and tools from accidentally connecting to production.

Why did my staging site break after search-replace?

Most commonly: you used a replace tool that isn’t serialization-safe and corrupted serialized data. Use wp search-replace (or an equivalent WordPress-aware tool), then rerun it carefully.

Can I push my staging database to production to deploy changes?

A full DB overwrite is rarely safe. Push code, then reproduce settings changes explicitly. If you must move data, scope it to specific tables and confirm they don’t contain live orders or submissions.

How do I keep staging from showing up in Google?

Use Basic Auth or IP allowlisting (best), plus X-Robots-Tag: noindex and a Disallow: / robots file. Don’t rely on robots.txt alone.

Summary: staging turns risky updates into routine work

A staging site isn’t a luxury in 2026. It’s how you update WordPress without gambling with uptime or revenue.

Clone files and the database. Replace URLs safely. Lock staging down. Suppress emails.

Then push code back to production using a clear checklist.

If you want staging and production to behave consistently under load, run them on infrastructure that stays predictable.

Start with a HostMyCode VPS, and move to dedicated servers once traffic and workflows justify it.

WordPress staging site tutorial (2026): Create a safe clone on your VPS, test updates, and push changes | HostMyCode