
Most WordPress outages don’t start with a “hack.” They start with a routine update that changes a database table, raises PHP requirements, or clashes with caching.
This WordPress staging site tutorial shows a repeatable workflow on an Ubuntu VPS. You’ll clone production, lock down staging, test updates, and deploy with a controlled cutover.
You can run this on any modern VPS. NVMe storage and enough RAM for PHP-FPM (plus your caching layer) makes it smoother.
If you want a clean server to follow along, start with a HostMyCode VPS and a fresh Ubuntu LTS image.
What you’ll build (and what you won’t)
You’ll create a staging copy at staging.yourdomain.com on the same server as production. It’s fast, inexpensive, and it matches how many small teams and agencies work.
- Production:
https://yourdomain.com - Staging:
https://staging.yourdomain.com(protected + no indexing) - Database: cloned from production, then isolated
- Deploy: controlled rsync + optional “maintenance window” for final DB sync
This is not a CI/CD pipeline or a container platform. It’s an admin-friendly workflow you can run reliably.
It also holds up when you’re tired and in a hurry.
Prerequisites checklist
- Ubuntu server with Nginx or Apache (examples below use Nginx)
- Working production WordPress site
- DNS control for your domain
- SSH access with sudo
- Let’s Encrypt (Certbot) installed, or ability to install it
If your VPS isn’t hardened yet, do that first. It helps prevent password-based SSH, wide-open ports, and skipped security updates.
Follow: server hardening steps for a new Ubuntu VPS.
Step 1: Add staging DNS and lower TTL before changes
Create the staging DNS record first. If staging is on the same VPS as production, it should point to the same public IP.
- Create an
Arecord:staging→YOUR_SERVER_IP - Set TTL to 300 seconds (5 minutes) a few hours before you do cutovers
If you’re also moving DNS (provider change, nameserver switch), use a no-downtime sequence: DNS migration without downtime.
That sequence helps prevent email breakage while you validate staging.
Step 2: Create a staging web root and lock file permissions
Create a separate directory for staging. Keep ownership aligned with your web stack (often www-data on Ubuntu).
sudo mkdir -p /var/www/staging
sudo chown -R www-data:www-data /var/www/staging
sudo find /var/www/staging -type d -exec chmod 755 {} \;
sudo find /var/www/staging -type f -exec chmod 644 {} \;
If production lives elsewhere (for example /var/www/yourdomain), note that path now.
You’ll copy from it next.
Step 3: Create a staging database and user
Even if staging shares the same MySQL/MariaDB server, give it its own database and credentials.
This separation matters the first time an update goes sideways.
Example using MariaDB/MySQL:
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;
Tip: store the password in a password manager. Don’t leave it in shell history or shared notes.
Step 4: Clone production files to staging with rsync
Don’t “approximate” production. Clone it exactly, then change only what you need.
Rsync works well because you can re-run it. It only copies deltas.
Example paths (adjust to your environment):
- Production:
/var/www/yourdomain - Staging:
/var/www/staging
sudo rsync -aHAX --delete \
--exclude='wp-content/cache/' \
--exclude='wp-content/uploads/cache/' \
/var/www/yourdomain/ /var/www/staging/
If your caching plugin writes large cache trees, exclude them.
This keeps the sync fast and avoids “stale cache” behavior after cloning.
Step 5: Dump production database and import into staging
Do this during a quieter period. For most SMB sites, a dump and import finishes quickly.
If your database is huge, you’ll need a more careful plan. This guide stays focused on the common case.
Export production DB:
mysqldump --single-transaction --quick --routines --triggers \
-u prod_db_user -p prod_db_name > /tmp/prod.sql
Import into staging:
mysql -u wp_staging_user -p wp_staging < /tmp/prod.sql
Clean up the dump after import:
shred -u /tmp/prod.sql
Step 6: Point staging wp-config.php to the staging database
Edit the config file in your staging directory:
sudo nano /var/www/staging/wp-config.php
Update these values:
define('DB_NAME', 'wp_staging');
define('DB_USER', 'wp_staging_user');
define('DB_PASSWORD', 'USE_A_LONG_RANDOM_PASSWORD');
define('DB_HOST', 'localhost');
You can keep your production salts/keys for basic testing.
If you want fewer cross-environment cookie quirks, generate separate keys for staging.
Step 7: Fix URLs in the staging database (the part everyone forgets)
After cloning, WordPress still thinks it lives on the production domain. You must rewrite URLs to the staging domain.
Don’t do this with a naive SQL replace. Serialized data will break.
Use WP-CLI instead.
If WP-CLI isn’t installed, on Ubuntu you can install it like this:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
Run the search-replace from the staging directory:
cd /var/www/staging
sudo -u www-data wp search-replace 'https://yourdomain.com' 'https://staging.yourdomain.com' --all-tables --precise
Then set the site URLs explicitly. This helps when plugins filter option values.
sudo -u www-data wp option update home 'https://staging.yourdomain.com'
sudo -u www-data wp option update siteurl 'https://staging.yourdomain.com'
Step 8: Configure your web server vhost for staging
On Nginx, add a new server block. On Ubuntu, these usually live under /etc/nginx/sites-available/.
sudo nano /etc/nginx/sites-available/staging.yourdomain.com
server {
listen 80;
server_name staging.yourdomain.com;
root /var/www/staging;
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 ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ {
expires 7d;
add_header Cache-Control "public";
}
}
Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/staging.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
If production has custom Nginx rules, keep staging close.
Many “it only breaks in prod” issues come from different caching, headers, or PHP-FPM behavior.
If you need to align configs, use: Nginx setup and tuning for WordPress.
Step 9: Add HTTPS with Let’s Encrypt and force safe defaults
Use HTTPS on staging. It’s the quickest way to catch mixed-content issues and cookie/session oddities before production.
If you haven’t set up Certbot on this server, follow: Let’s Encrypt setup on Ubuntu for Nginx/Apache.
Once Certbot is installed:
sudo certbot --nginx -d staging.yourdomain.com
After issuance, confirm auto-renew works:
sudo certbot renew --dry-run
Step 10: Protect staging with HTTP auth and block search indexing
A different hostname doesn’t make staging private.
Treat staging like a real attack surface and put a gate in front of it.
Option A (recommended): HTTP Basic Auth at Nginx level
sudo apt-get update
sudo apt-get install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-staging youruser
Edit the staging server block and add:
location / {
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd-staging;
try_files $uri $uri/ /index.php?$args;
}
sudo nginx -t
sudo systemctl reload nginx
Option B: WordPress discourages indexing (still do HTTP auth)
cd /var/www/staging
sudo -u www-data wp option update blog_public 0
Add a robots.txt as a second layer:
sudo nano /var/www/staging/robots.txt
User-agent: *
Disallow: /
Step 11: Make staging safe for email, payments, and external APIs
This is where staging pays for itself. Stop anything that could email real people or charge real cards.
- Email: point SMTP plugins to a sink mailbox, or disable outgoing mail at the plugin level.
- WooCommerce: switch payment gateways to test mode, and confirm webhooks point to staging.
- CDN/WAF: keep staging off the public CDN unless you’re deliberately testing edge behavior.
Quick diagnostic: check whether PHP can send mail from staging.
In many setups, you actually want this to fail.
cd /var/www/staging
sudo -u www-data wp eval 'wp_mail("you@example.com","staging test","If you got this, staging can send mail.");'
If you run mail services on the same VPS, keep deliverability clean.
Separate test traffic from production.
These guides help when email starts acting up: deliverability troubleshooting and PTR record setup for fewer rejections.
Step 12: Create a repeatable “update and test” routine
Once staging exists, the routine is straightforward.
Each update cycle should follow the same pattern:
- Sync production → staging (files + DB)
- Run updates on staging
- Test the same pages and actions every time
- Deploy changes to production in a controlled way
A. Re-sync files (fast)
sudo rsync -aHAX --delete \
--exclude='wp-content/cache/' \
--exclude='wp-content/uploads/cache/' \
/var/www/yourdomain/ /var/www/staging/
B. Re-sync database (overwrite staging DB)
mysqldump --single-transaction --quick \
-u prod_db_user -p prod_db_name | mysql -u wp_staging_user -p wp_staging
C. Re-run URL rewrite (safe to repeat)
cd /var/www/staging
sudo -u www-data wp search-replace 'https://yourdomain.com' 'https://staging.yourdomain.com' --all-tables --precise
D. Apply updates on staging
cd /var/www/staging
sudo -u www-data wp plugin update --all
sudo -u www-data wp theme update --all
sudo -u www-data wp core update
sudo -u www-data wp core update-db
E. Quick test checklist (don’t skip it)
- Homepage loads (logged out + logged in)
- Search works (if applicable)
- Contact form submits (ensure it doesn’t email real recipients)
- Checkout flow (WooCommerce) in test mode
- Admin: posts list, media library, editor, plugin pages
- Performance sanity: no new 5xx spikes in logs
A couple log checks catch most issues early:
sudo tail -n 80 /var/log/nginx/staging.error.log
sudo tail -n 80 /var/log/php8.3-fpm.log
Step 13: Deploy to production without gambling on “it worked on staging”
Staging reduces risk, but it doesn’t remove it.
Keep production deploys structured and boring.
- Take a backup (files + DB) you can restore quickly
- Put the site into maintenance briefly (optional, but recommended for WooCommerce)
- Deploy files first, then run DB updates if needed
- Verify, then disable maintenance
A. Backup before you touch production
If you don’t have a tested backup system, fix that first.
A backup you’ve never restored is a story, not a safety net.
Use: VPS backup strategy (3-2-1 + restore tests).
B. Put production in maintenance mode (optional, good for stores)
cd /var/www/yourdomain
sudo -u www-data wp maintenance-mode activate
C. Deploy files from staging to production
If staging contains test content or experimental uploads, deploy only code.
A common approach is to ship:
wp-admin/wp-includes/- plugin/theme directories under
wp-content/(not uploads)
Example deploying plugins and themes only:
sudo rsync -aHAX --delete \
/var/www/staging/wp-content/plugins/ /var/www/yourdomain/wp-content/plugins/
sudo rsync -aHAX --delete \
/var/www/staging/wp-content/themes/ /var/www/yourdomain/wp-content/themes/
If you updated WordPress core, deploy those directories too:
sudo rsync -aHAX --delete /var/www/staging/wp-admin/ /var/www/yourdomain/wp-admin/
sudo rsync -aHAX --delete /var/www/staging/wp-includes/ /var/www/yourdomain/wp-includes/
D. Run database updates on production (only if required)
cd /var/www/yourdomain
sudo -u www-data wp core update-db
E. Turn off maintenance mode and verify
sudo -u www-data wp maintenance-mode deactivate
Then do a quick health check:
curl -I https://yourdomain.com | head
sudo tail -n 80 /var/log/nginx/error.log
Step 14: Common staging failures and fast fixes
These are the issues people hit most often.
Each item includes quick checks that solve many cases.
Staging shows production content or redirects to production
- Confirm
homeandsiteurloptions:
cd /var/www/staging
sudo -u www-data wp option get home
sudo -u www-data wp option get siteurl
- Re-run WP-CLI search-replace (serialized-safe):
sudo -u www-data wp search-replace 'yourdomain.com' 'staging.yourdomain.com' --all-tables --precise
HTTPS works on production but staging shows certificate errors
- Confirm DNS points to the correct IP.
- Check Nginx is listening for the staging name.
- Re-run Certbot for the staging hostname.
If renewals fail later, use a real checklist: TLS renewal troubleshooting.
Staging is slow, but production is fine
- Staging often runs without caching due to auth or robots rules. That’s expected.
- Ensure PHP-FPM socket/version matches production.
- Check for missing OPcache or too low memory limit in PHP.
If you want staging to mirror production performance more closely, align your Nginx + PHP-FPM settings: VPS performance optimization for WordPress.
Step 15: Optional hardening for agencies and resellers
If you manage multiple client sites, staging can become an easy target. That’s especially true when every staging subdomain is reachable.
Two controls make a big difference:
- Firewall restriction: allow staging only from your office IP/VPN, plus your monitoring IPs.
- Separate system user: run staging under a different UNIX user to reduce blast radius.
After tightening firewall rules, verify you didn’t break Let’s Encrypt or SSH.
This helps when rules bite back: UFW firewall troubleshooting.
Summary: your staging workflow in 10 minutes
- Create
staging.yourdomain.comDNS record - Clone files with rsync (exclude caches)
- Clone DB and point
wp-config.phpto staging DB - Rewrite URLs with WP-CLI (serialized-safe)
- Add Nginx/Apache vhost + Let’s Encrypt
- Protect staging with HTTP auth and block indexing
- Update on staging, test, then deploy with a backup in place
If you want the same workflow with less server maintenance, run staging on a plan with predictable resources and real support.
managed VPS hosting is a good fit when client sites pay the bills and downtime isn’t acceptable.
If your current hosting makes staging a chore—slow disks, limited SSH access, or messy SSL renewals—move this workflow to infrastructure that supports it cleanly. Start with a HostMyCode VPS, or choose managed VPS hosting if you want patching, monitoring, and help with tricky migrations.
FAQ
Should staging be on the same VPS as production?
For many small sites, yes. It keeps the setup simple and inexpensive.
If you handle high traffic or regulated data, put staging on a separate VPS to reduce risk.
Do I need to copy uploads to staging every time?
Not always. For update testing, plugin/theme/core changes are usually the focus.
If you’re chasing media bugs or rebuilding layouts, sync uploads as well.
How do I stop staging from sending real emails?
Disable SMTP plugins on staging, force a sink address, or block outbound SMTP at the server level.
Test forms, but don’t let them notify real customers.
What’s the safest way to deploy database changes?
If updates change the schema, schedule a short maintenance window, back up first, update production, and run wp core update-db.
For stores, avoid schema changes during peak traffic.
Can I use this workflow on shared hosting?
Parts of it, yes (subdomain + plugin-based cloning).
But SSH, WP-CLI, and clean rsync deploys are much easier on a VPS where you control the filesystem and web server.