
Table of Contents
Set up staging on its own subdomain with a separate database, block crawlers with HTTP auth or robots restrictions, and never sync production data downward. Copy files and database from live to staging only, test changes, then push files and specific database rows upward with a migration tool. You know it worked when the staging site loads on the private URL, is invisible in Google, and production stays untouched until you deliberately deploy.
Preparation Before You Start
Before touching anything, snapshot your production server and note the WordPress core version you run in 2026, typically 6.7 or later, plus PHP 8.2 or 8.3. Confirm you have SSH access and a tool such as WP-CLI 2.11 installed. Decide where staging lives: a subdomain like staging.example.com on the same server is fine for small sites, but keep it in a separate directory from the live web root. Check your host allows additional MySQL databases; if you are unsure about server layout, review Deploy WordPress on Baota LNMP: Root, Perms, Rewrites for reference paths. Finally, pick a migration plugin or script you trust, because you will use it in both directions later.
Step Process: Build Staging on a Subdomain with a Separate Database
- Create a new subdomain and point it to a directory such as
/var/www/stagingoutside or beside the live root. - Provision a fresh MySQL database and user dedicated to staging only.
- Copy production files into the staging directory with rsync, excluding cache folders.
- Export the live database and import it into the new staging database.
- Update the staging
wp-config.phpto use the staging database and home URLs. - Lock the subdomain behind HTTP authentication or IP restriction.
Commands for Files and Database
Run these on the server. Replace names to match your setup.
rsync -av --exclude='wp-content/cache/' /var/www/html/ /var/www/staging/
mysql -u root -p -e "CREATE DATABASE staging_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p -e "CREATE USER 'staguser'@'localhost' IDENTIFIED BY 'strong_password_here';"
mysql -u root -p -e "GRANT ALL PRIVILEGES ON staging_db.* TO 'staguser'@'localhost';"
mysqldump -u root -p live_db | mysql -u staguser -p staging_db
Edit wp-config.php on Staging
Open /var/www/staging/wp-config.php and change the database constants and URLs. Use the $_SERVER['HTTP_HOST'] trick or hardcode after import:
define( 'DB_NAME', 'staging_db' );
define( 'DB_USER', 'staguser' );
define( 'DB_PASSWORD', 'strong_password_here' );
define( 'WP_HOME', 'https://staging.example.com' );
define( 'WP_SITEURL', 'https://staging.example.com' );
Then search and replace the old domain inside the staging database with WP-CLI, not a raw SQL replace, to keep serialized data intact:
Get a Quote
Email + requirement + budget range — we reply with a quote within 24 hours.
cd /var/www/staging
wp search-replace 'https://example.com' 'https://staging.example.com' --all-tables --dry-run
wp search-replace 'https://example.com' 'https://staging.example.com' --all-tables
Common pitfall: forgetting serialized arrays. Always use WP-CLI or a migration plugin, never a plain SQL UPDATE, or widgets and options will break. Verify by loading https://staging.example.com and checking Settings → General shows the staging URL.
Blocking Search Engines on Staging
A staging site that gets indexed causes duplicate content and leaks unfinished work. The strongest method is server-level protection. For Nginx add this inside the staging server block:
location / {
auth_basic "Staging Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ /index.php?$args;
}
Generate the password file with htpasswd -c /etc/nginx/.htpasswd staginguser. For Apache use a similar directive in .htaccess or the vhost. As a second layer, set the WordPress visibility option:
wp option update blog_public 0
This writes blog_public as 0 so /robots.txt serves a disallow header. Verify by fetching https://staging.example.com/robots.txt and seeing User-agent: * followed by Disallow: /. Also confirm Google cannot reach it: try loading the URL logged out from another network; you should get a 401 login prompt. Pitfall: some hosts put staging behind a proxy that bypasses auth; if so restrict by IP in the firewall. Rollback is instant—remove the auth block and set blog_public back to 1.
Database Sync Direction: Always Downward by Default
Data must flow from production to staging, never the reverse during daily work. Production holds real orders, users, and form entries; staging holds tests. If you sync upward accidentally you overwrite live customer records. The safe rule is one-way: live → staging. When you need fresh content, re-run the dump and search-replace shown earlier. If you use a plugin like WP Staging or Duplicator, choose the “clone” direction from live to staging only.
For teams that need a safety net before major structural work, our Custom WordPress Development team sets up managed pipelines so the downward rule is enforced by script. To verify direction, check timestamps: after a refresh, staging posts should match production IDs and dates, while production should show no new test rows. Pitfall: developers sometimes “just quickly” push a staging user table up; that deletes live accounts. If that happens, restore production from your pre-sync backup immediately.
Pushing Back to Production Safely
When staging changes are approved, deploy upward with care. Never overwrite the whole production database. Instead push specific things: theme files, plugin files, and new media. Use rsync for code:
rsync -av --exclude='wp-config.php' --exclude='wp-content/uploads/' /var/www/staging/wp-content/themes/ /var/www/html/wp-content/themes/
rsync -av --exclude='wp-config.php' /var/www/staging/wp-content/plugins/ /var/www/html/wp-content/plugins/
For database changes made on staging, such as new pages or settings, export only those tables. Example: move a new page by noting its ID on staging, then inserting the post row and postmeta into live via a migration tool that maps IDs. Better yet, recreate structural changes manually on production through the admin so you control every row. Before any push, take a full backup:
mysqldump -u root -p live_db > /backups/live_before_push_2026.sql
tar -czf /backups/live_files_2026.tar.gz /var/www/html
After pushing, clear production cache and run wp cache flush. Verify by visiting the live site in a private window and testing the changed feature. If something breaks, restore from the backup you just made and re-sync staging from the restored production so both match again. Another useful read is our WordPress Pre-Launch Checklist: 26 Things to Verify to confirm nothing is missed at go-live.
Verification and Rollback Summary

| What to check | How to verify | Rollback action |
|---|---|---|
| Staging loads on subdomain | Visit URL, check Settings → General | Fix wp-config or DNS |
| Search engines blocked | 401 prompt, robots.txt disallow | Remove auth, set blog_public 1 |
| Data flowed downward | Compare post IDs and dates | Restore production from backup |
| Push succeeded | Test feature on live site | Restore files and DB backup |
Action Checklist
- Create subdomain and separate staging database, then import live data with WP-CLI search-replace.
- Lock staging with HTTP auth and set
blog_publicto 0. - Confirm sync direction is live to staging only.
- Backup production before pushing any code or rows upward.
- Verify live site after push and know your restore command.
Need expert help building this pipeline? See our WordPress development services for managed staging and deployment. More guides await in the technical articles index.
Frequently asked questions
Can I use a subdirectory instead of a subdomain for staging?
Yes, but a subdomain with its own database is cleaner. Subdirectories share the live database unless you configure carefully, raising the risk of accidental cross-edits.
What if my host does not allow SSH or WP-CLI?
Use a plugin-based cloner that runs over HTTP, then still protect the staging URL with password authentication through the host panel.
How often should I refresh staging from production?
Before every major project phase. Weekly is common for active sites, but always refresh right before you start new theme or plugin testing.
