A staging site is a private copy of your website where you can break things safely. You apply the update, try the redesign, install the unfamiliar plugin, and watch what happens, while the real site carries on serving real visitors. If the experiment goes wrong, you delete the copy and start again. If it goes right, you repeat the change on the live site with a good deal more confidence than you had an hour earlier.
It is the cheapest insurance in web hosting. A copy costs a few hundred megabytes of disk space and about half an hour of your time, and it saves the sort of evening that begins with a white screen and a customer email. Most of the support tickets I have seen titled "the site is down after the update" would have been a non-event on a staging copy.
This guide walks through building one by hand, which works on any host, then covers locking it down, testing on it, moving changes across, and keeping it from becoming a liability. If your host has a one-click staging button, the steps below still describe what that button does, which is useful when it does something unexpected.
What staging has to be
The whole point of a staging site is that it behaves like the live one. A copy that differs in important ways will pass tests that the live site then fails. So before you build anything, decide what "the same" means for your site.
The files and the database should come from live, recently. The PHP version should match, along with the web server (Apache or nginx), the memory limit and the major extensions. The same plugins and theme should be active at the same versions. What should not match is everything that touches the outside world: real email, real payments, search engine access, and the public.
Step by step: building the copy by hand
These steps assume a typical shared or VPS account with SSH, a control panel for databases, and WordPress as the application. The shape is the same for other PHP applications; only the configuration file changes.
- Create a subdomain,
staging.example.com, in your control panel, with its own document root, for example/home/example/staging. Check that DNS resolves it withdig +short staging.example.com. - Create a new, empty database and a new database user for it. Never point staging at the live database, not even temporarily. This is the single most expensive mistake available.
- Copy the files:
cp -a /home/example/public_html/. /home/example/staging/. The-akeeps permissions and timestamps. - Dump the live database and load it into the new one:
mysqldump -u live_user -p live_db > live.sql
mysql -u stg_user -p stg_db < live.sql
- Edit
/home/example/staging/wp-config.phpso it uses the new database name, user and password. Adddefine('WP_ENVIRONMENT_TYPE', 'staging');so plugins that care can tell where they are. - Rewrite the addresses stored in the database (next section).
- Lock the copy down (the section after that) before you open it in a browser.
Rewriting addresses without corrupting data
WordPress stores its own address in the database, and a good many plugins store full URLs in options, widgets and page-builder content. If you leave them alone, the staging site will load its stylesheets from the live site, or redirect you straight back to it. That makes for a confusing afternoon in which you edit one site while looking at the other.
Much of that data is serialised: a PHP string such as s:24:"https://example.com/logo" records its own length. A plain find-and-replace in a text editor changes the text but not the length, and the value silently stops loading. WP-CLI's search-replace understands serialised data and fixes the lengths as it goes:
wp search-replace 'https://example.com' 'https://staging.example.com' \
--all-tables --skip-columns=guid --dry-run
wp search-replace 'https://example.com' 'https://staging.example.com' \
--all-tables --skip-columns=guid
Run the dry run first and read the counts. Skipping the guid column is deliberate: those values are permanent identifiers for feeds, not links. Without WP-CLI, a dedicated search-replace tool that handles serialised data does the same job; a raw SQL UPDATE ... REPLACE() does not.
http:// addresses in old content, or a www variant, run the replacement for each form you find. Check with wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%example.com%'" afterwards.Locking the copy down
An open staging site causes three separate kinds of trouble. Search engines index it, so you end up with a duplicate of your own site competing against you. Strangers find it, and an unpatched test copy is an easy target. And it can send real email, charge real cards or call real services, because it holds live credentials.
Work through these in order, before anyone loads a page:
- Password-protect the whole subdomain. With Apache, create a file with
htpasswd -c /home/example/.htpasswd testerand add to the staging.htaccess:AuthType Basic,AuthName "Staging",AuthUserFile /home/example/.htpasswdandRequire valid-user. - Tell WordPress to discourage indexing:
wp option update blog_public 0. Treat this as a courtesy to crawlers, not as security. The password is the real barrier. - Stop outgoing mail. Either use a staging-mode setting in your SMTP plugin, or drop a small must-use plugin in
wp-content/mu-plugins/that returns false from thepre_wp_mailfilter. - Switch payment gateways to their test or sandbox mode, and replace live API keys with test keys.
- Disable scheduled jobs that talk to customers, such as abandoned-cart emails and renewals.
What changes between hosting types
How much of this you do yourself depends on where the site lives.
| Hosting | Typical staging route | Watch for |
|---|---|---|
| Shared | Panel tool or manual subdomain copy | Same PHP version is usually enforced per domain; check it, as a subdomain can be set differently |
| VPS | A second virtual host on the same server, or a separate small server | A staging site on the same server shares its CPU and memory, so a heavy test can slow live |
Testing on the copy
Apply the change you came to test, then walk through the paths that make money or cause support tickets. Log in and out. Submit each form and confirm where the message would have gone. Search for something. Add a product to a basket and get as far as the payment page. Open some old posts, a category page and the home page on a phone-sized window.
Then read the logs, because many faults do not show on screen. Turn on debugging on staging only: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true);, then tail -f wp-content/debug.log while you click around. Deprecation notices about a plugin are an early warning that the next PHP version will break it. The web server's error log, usually under logs/ or /var/log/, is the other place to look.
Getting the change onto live
There are two honest ways to do this, and the right one depends on what kind of change you made.
For updates and settings changes, repeat the steps on live. Take a fresh backup, pick a quiet hour, and apply the same updates in the same order. This is slower than pushing a copy across, but nothing on the live site is overwritten, and any orders or comments that arrived while you were testing are safe.
For a redesign or a large structural change, you may push the staging files and database over live. That is only safe if the live content has not changed since you copied it. On a brochure site that is easy to guarantee. On a shop, a forum or a busy blog, it is not: you would wipe out every order, signup and comment since the copy. In that case, push the code and theme files across, and apply database changes by hand or with a migration tool.
Keeping staging useful and then removing it
A staging copy that is three months old tests nothing, because live has moved on. Refresh it from live before each round of changes: re-export the database, re-run the search-replace, and copy changed files.
When you are finished, delete the copy: the files, the database, the database user and the subdomain. An old, forgotten staging site is usually running outdated plugins, often with a weak password, and holding a full copy of your customer data. I have seen more than one breach that started on a test site nobody remembered.
Commands worth running
curl -I https://staging.example.com/should return401 Unauthorizedwithout credentials.curl -s https://staging.example.com/robots.txtand the page source should show a noindex signal.wp option get siteurlandwp option get homeon staging should show the staging address.wp eval 'echo PHP_VERSION;'on both sites should print the same version.- The troubleshooting guide covers failures you do not recognise.
Smaller questions
Is a staging plugin as good as doing it by hand?
For most small sites, yes. The plugin does the same copy and search-replace for you. Look at what it does with email, indexing and scheduled tasks, because those defaults vary.
Does staging protect me from every bad update?
No. It catches faults that show up in the copy. Problems that depend on real traffic, real data volume or timing may only appear on live, which is why a backup is still non-negotiable.
Checklist
- Staging runs on its own subdomain, files folder and database user.
- PHP version and server software match live.
- Addresses rewritten with a serialisation-aware tool; dry run checked.
- Password on the whole subdomain; indexing discouraged.
- Outgoing email suppressed; payment gateways in sandbox mode.
- Key paths tested: login, forms, search, basket, checkout.
- Error and debug logs read after testing.
- Fresh live backup taken before the change is repeated or pushed.
- Staging refreshed before each use and deleted when finished.