How I Set Up a WordPress Staging Site on DigitalOcean: Subdomain and Temp Domain Approaches
In Part 1 of this series, we set up a production WordPress server on DigitalOcean from scratch, LEMP stack, SSL, Redis, the works. That server is running, it’s fast, and it’s handling real traffic. But here’s what I didn’t cover: what happens when you need to test a theme update, debug a plugin conflict, or try a major redesign without risking the live site? You need a staging environment. And if you’ve already followed Part 1, you have everything you need to set one up on the same droplet. No extra server costs, no managed staging plugins with their limitations. Just a second WordPress installation that mirrors production, isolated and safe.
I use staging environments on every client project. The number of times a “quick plugin update” has broken a live site is enough to make staging non-negotiable. This guide covers two approaches: a proper subdomain setup (staging.yourdomain.com) and a quick temporary domain for when you just need something fast.
Every command and config file is in a single GitHub gist you can fork and customize.
The Two Approaches
Before we start, let’s decide which approach fits your situation:
| Subdomain Staging | Temporary Domain | |
|---|---|---|
| URL | staging.yourdomain.com | your-ip:8080 or IP.nip.io |
| SSL | Yes (Let’s Encrypt) | No (or self-signed) |
| DNS needed | Yes (A record) | No |
| Setup time | ~20 minutes | ~10 minutes |
| Best for | Ongoing development, client previews | Quick tests, one-off debugging |
| Shareable | Yes, clean URL | Awkward but works |
My recommendation: if this is a site you maintain regularly, go with the subdomain approach. It takes 10 extra minutes and you’ll use it constantly. The temp domain option is for when you need staging in the next 5 minutes and don’t care about a pretty URL.
Both approaches share the same foundation, database clone, file copy, wp-config setup, and URL replacement. The only difference is how the web server routes traffic to the staging directory.
Approach 1, Subdomain Staging (staging.yourdomain.com)
This is the proper way to do it. You get a clean URL, SSL, and something you can share with clients for review. Let’s build it step by step.
Create the Staging Database
First, we create a separate database for staging and clone the production data into it. This is the most important isolation step, staging and production must never share a database.
The script generates a random password for the staging database user. Save it, you’ll need it for wp-config.php in the next step. The --single-transaction flag on mysqldump means it takes a consistent snapshot without locking your production tables. Your live site stays responsive during the clone.
Clone WordPress Files
Now we copy the production WordPress installation to a new directory. We copy everything, core files, themes, plugins, uploads. One thing to note: we deliberately don’t copy wp-config.php. The production config has the wrong database credentials and URLs. We’ll create a fresh one next.
For large sites with gigabytes of uploads, this copy can take a while. If you don’t need the full uploads directory for testing, you can skip it and add --exclude='uploads/' to the copy command. Media will show as broken images on staging, but the site will still function.
Configure wp-config.php for Staging
This is where staging diverges from production. The staging wp-config.php points to the staging database, enables debug mode, and sets the correct URLs. Key differences from production:
- Different database credentials, staging_user with its own password, pointing to wordpress_staging.
- Debug mode ON, WP_DEBUG, WP_DEBUG_LOG, and SCRIPT_DEBUG are all enabled. You want to see every warning and notice on staging.
- WP_ENVIRONMENT_TYPE = ‘staging’, WordPress 5.5+ recognizes this. Plugins can check it to disable production-only features.
- Auto-updates disabled, you don’t want WordPress updating itself on staging independently from production.
- Fresh salts, generate new ones from the WordPress salt API. Never reuse production salts or your auth cookies will work across both environments.
Fix URLs with WP-CLI
The cloned database still has production URLs everywhere, in post content, option values, widget settings, serialized data. WP-CLI’s search-replace handles all of this, including serialized PHP arrays that would break with a naive find-and-replace. Always run the dry run first. It shows you exactly how many replacements will happen in each table. If the numbers look wrong (like zero replacements, or thousands in a table you don’t recognize), investigate before proceeding.
The --precise and --recurse-objects flags are important. They handle nested serialized data correctly, without them, some plugin settings can get corrupted.
DNS Setup
Point staging.yourdomain.com to your droplet. You can do this through the DigitalOcean control panel, CLI, or API, the script shows all three options.
DNS propagation usually takes 2-5 minutes for DigitalOcean-managed domains. If you’re managing DNS elsewhere (Cloudflare, your registrar), it depends on your TTL settings. Use dig +short staging.yourdomain.com to check, once it returns your droplet IP, you’re good to proceed.
Nginx Server Block
Create an Nginx server block that routes staging.yourdomain.com to the staging directory. This is a separate config file from your production server block, they run independently. A few things worth noting:
- Separate log files, staging gets its own access and error logs. Keeps things clean when debugging.
- X-Robots-Tag header, tells search engines not to index this site, even if they somehow get past Basic Auth.
- Shorter cache TTL, static files cached for 1 hour instead of 30 days. You’re actively developing here, you want changes to show up quickly.
- debug.log blocked, since we have WP_DEBUG_LOG enabled, the debug log file is accessible via the web by default. This rule blocks that.
Enable the config and test:
sudo ln -s /etc/nginx/sites-available/staging.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL with Certbot
If you followed Part 1, you already have Certbot installed. Getting SSL for the staging subdomain is one command. Certbot modifies your Nginx config to add the SSL directives and sets up automatic HTTP-to-HTTPS redirect. The certificate auto-renews, Certbot’s systemd timer handles that.
At this point, visiting https://staging.yourdomain.com should show your cloned WordPress site. Same content, same theme, same plugins, but completely isolated from production.
Approach 2, Temporary Domain
Sometimes you don’t want to mess with DNS. Maybe you’re debugging an issue at 2 AM, or you just need to test something quickly and throw the staging site away afterward. The temp domain approach skips DNS entirely.
Using the Droplet IP with a Port
The simplest option: run staging on a different port. Your production site runs on ports 80/443. Staging runs on port 8080. No DNS, no SSL, just works.
You still need to do the database clone (gist 01), file copy (gist 02), wp-config setup (gist 03), and URL replacement (gist 04), but set the staging URL to http://your-droplet-ip:8080 instead of a subdomain.
Don’t forget to open the port in your firewall:
sudo ufw allow 8080/tcp
Using nip.io or sslip.io
Here’s a trick I use constantly: nip.io and sslip.io are free DNS services that resolve any subdomain containing an IP address to that IP. So 1.2.3.4.nip.io resolves to 1.2.3.4, automatically, no configuration needed.
This gives you a “real” domain name without touching DNS. It’s especially useful when you need to test something that requires a domain (like OAuth callbacks or certain WordPress plugins that reject bare IP addresses).
Nginx Config for Temp Domain
The config file covers both the port-based and nip.io approaches: The tradeoff with temp domains: no SSL (you’d need to use self-signed certs or skip HTTPS entirely), and the URL is ugly. But for quick debugging or testing a plugin update, it’s perfect. When you’re done, remove the Nginx config and delete the staging directory. Five-minute cleanup.
Lock Down Staging
A staging site that’s accessible to the public is a problem. Search engines will find it, index duplicate content, and your SEO takes a hit. Bots will find the login page and start brute-forcing. We need three layers of protection.
Basic Authentication
HTTP Basic Auth is the first line of defense. Anyone visiting the staging URL gets a username/password prompt before they see anything.
We exclude wp-cron.php and the REST API from Basic Auth because some plugins need to hit these endpoints. If your staging site doesn’t use any plugins that need external API access, you can remove those exceptions for tighter security.
Block Search Engines
The robots.txt file tells well-behaved crawlers to stay away: This works alongside the X-Robots-Tag header we set in the Nginx config (belt and suspenders).
Staging MU-Plugin
This is the piece that makes staging really safe. Drop this MU-plugin into wp-content/mu-plugins/ and it does three critical things: adds a bright red “STAGING” banner to the admin bar (so you never accidentally edit the wrong site), blocks all outgoing emails (so staging doesn’t email your real users), and adds a meta robots tag as a third layer of indexing protection.
The email blocking is the most important part. I’ve seen staging sites send password reset emails, order confirmations, and newsletter blasts to real customers. The pre_wp_mail filter catches everything and logs it to debug.log instead of sending it.
There’s also a commented-out section for blocking external HTTP requests to payment gateways, analytics, and email services. Uncomment it if your staging site might accidentally hit Stripe, Google Analytics, or Mailchimp.
Syncing Between Production and Staging
A staging environment is only useful if it reflects production. These two scripts handle the sync in both directions.
Production to Staging (Fresh Copy)
Run this whenever you want a fresh staging environment that matches production exactly. It backs up the current staging state, clones the production database, syncs wp-content, and fixes URLs. I run this at the start of every development sprint. Fresh data means you’re testing against real content, real user accounts, and real plugin settings, not stale data from three weeks ago.
The rsync command excludes the cache directory, the upgrade directory, and the staging MU-plugin. You don’t want production’s cache on staging, and you definitely don’t want to overwrite the staging safeguards with production files that don’t have them.
Staging to Production (Push Changes)
This is the dangerous one. Use it when you’ve tested everything on staging and you’re ready to push changes live. The script forces you to confirm, creates a full production backup first, and lets you choose exactly what to push. Most of the time, you’ll use option 1 (themes only) or option 3 (themes and plugins). Full database pushes are rare, usually only for major redesigns where content structure changed. The backup commands at the bottom of the output tell you exactly how to restore if something goes wrong.
A word of caution: option 6 (everything) is essentially replacing your production site with staging. Make sure you’ve tested thoroughly, and ideally do this during a maintenance window when traffic is low.
Multisite Considerations
If your production site runs WordPress Multisite, the staging setup needs a few extra tweaks. The Nginx config needs additional rewrite rules for sub-site file handling, and the URL replacement needs to run across all sites in the network. Subdomain Multisite is the trickiest scenario. Each sub-site has its own domain (site1.example.com, site2.example.com), so staging all of them requires either wildcard DNS and SSL, or converting to subdirectory mode on staging. The config file covers both options.
If you don’t run Multisite, skip this entirely.
Verification Checklist
After setting everything up, run the verification script. It checks connectivity, Basic Auth, robots blocking, file permissions, database connection, environment type, and debug mode. You should see all green checkmarks on the critical items. The manual checks at the bottom are things the script can’t verify automatically, log into wp-admin and make sure you see the red STAGING banner, submit a test form and check debug.log to confirm emails are being blocked, and look at the browser console for mixed-content warnings (HTTP resources on an HTTPS page).
When to Use Staging
Every time. Seriously. Here’s my workflow:
- Start of sprint, run the prod-to-staging sync script. Fresh data.
- Development, all changes happen on staging first. Theme edits, plugin updates, new features.
- Testing, verify everything works on staging. Check debug.log for warnings. Test forms, payment flows, user registration.
- Push, when everything passes, use the staging-to-prod script to push only what changed.
- Verify production, quick smoke test on the live site to confirm the push worked.
This workflow has saved me from breaking production more times than I can count. A WooCommerce update that silently breaks checkout. A theme update that shifts the layout on mobile. A PHP version conflict with an old plugin. All caught on staging, all fixed before any customer noticed.
Every file referenced in this guide is available in the GitHub gist. Fork it, customize the paths and domain names, and you have a repeatable staging setup for every project.
You Might Also Like
- How I Set Up WordPress on DigitalOcean: Complete Server to First Post Guide, Part 1 of this series. The production server setup that this staging guide builds on.
- How We Cleaned Malware From 17 WordPress Sites in One Day, what happens when things go wrong on production, and why having a clean staging environment to test recovery is critical.
- Multi-Vector WordPress Malware Protection, staging isn’t just for development. It’s also where you test security patches before rolling them out to production.
We specialize in web design & development, search engine optimization and web marketing, eCommerce, multimedia solutions, content writing, graphic and logo design. We build web solutions, which evolve with the changing needs of your business.