Deployment guide

WordPress client staging: build it, test updates, then deploy

Deploy on a VPS Cloud →

Tutorial

WordPress client staging: build it, test updates, then deploy

Deployment12 min read10 steps

A core or plugin update takes seconds and can cost hours, in front of a client who sells online. Yet a badly isolated staging site is worse than none: it gets indexed, sends real emails to the site's customers, or overwrites production URLs on the way back. This guide builds a safe copy of a client WordPress site step by step, tests updates one at a time, reads errors in `debug.log`, then moves the changes to production without overwriting the live database, with a rollback ready.

Contents· Why a staging site rather than testing on the live site1/14
  1. 01Why a staging site rather than testing on the live site
  2. 02What a staging site gives you
  3. 03Prerequisites, with numbers, before copying
  4. 04Build the staging site, step by step
  5. 05Really isolate it: noindex is not enough
  6. 06Neutralise emails, payments and scheduled tasks
  7. 07Checklist before touching a single plugin
  8. 08Test updates, one at a time
  9. 09When the update breaks: reading debug.log
  10. 10Troubleshooting: the other symptoms you really meet
  11. 11Files or database: what moves, and how
  12. 12Moving to production, and being able to go back
  13. 13The hardening reflex
  14. 14A routine that holds at portfolio scale

Why a staging site rather than testing on the live site

A form plugin that no longer initialises, a theme that loses its layout, the screen "There has been a critical error on this website": these failures follow an update that looked harmless. With three sites to manage, caution is enough. With thirty, you either update in the dark or stop updating, and then the unpatched vulnerability is what costs you.

A staging site is a live copy of the site, on a separate address, with its own database. You can break what you like, read the errors, fix them, then replay the already validated step on production. It does not replace the backup: it makes restoring one rarely necessary.

What a staging site gives you

  • Updates without surprises — every core, theme or plugin change is seen on screen before it reaches visitors.
  • A readable diagnosis — errors go to a log, not in front of the client, and the cause is found before they call.
  • A shared review — the client or their project manager signs off on a separate address, without touching the public site.
  • A change log — you note the version before and after for each plugin, and replay exactly the same on production.
  • A trial for new things — new theme, new page builder, new payment module: everything is judged on a copy.
  • A prepared rollback — the backup is made and checked before the test, not after the incident.

Prerequisites, with numbers, before copying

Count three times the size of the live site in disk space: the site, its copy and the database export. Read the size of wp-content with du -sh wp-content and that of the database in phpMyAdmin or with wp db size. Plan one subdomain per client, for example staging.your-domain.com, and the same PHP version as production: a staging site on a newer PHP validates a configuration that is not the client's. That version is shown under Tools › Site Health › Info, and the versions still supported are kept up to date at php.net/supported-versions.php.

You also need a terminal with wp-cli (SSH on an account that allows it, or a VPS). This guide describes the manual copy, files then database, which shows exactly what is copied. If WP Toolkit is available on your account, its cloning produces that copy for you (see the guide "Install WordPress from cPanel"); the rest of the guide (noindex, authentication, emails switched off, tests) then applies unchanged. For installing a new site from cPanel, see the dedicated guide.

Build the staging site, step by step

  1. Back up production before any write

    From the live site's folder:
    mkdir -p ~/preprod-backups
    wp db export ~/preprod-backups/before-staging.sql --single-transaction
    tar -czf ~/preprod-backups/files-before-staging.tar.gz -C ~/public_html .
    Check that both files exist and are not empty with ls -lh ~/preprod-backups. The plan's own backups do not replace this step: their frequency depends on the plan, so they may be several days old.

  2. Create the subdomain and its folder

    Create staging.your-domain.com in cPanel (the guide "Create a subdomain in cPanel" details the screen) with its own document root, preferably outside public_html, for example ~/staging.your-domain.com. A folder placed inside public_html would also be reachable through an address of the live site. Wait until the subdomain's TLS certificate is issued before testing.

  3. Copy the files

    rsync -a --exclude='wp-content/cache/' ~/public_html/ ~/staging.your-domain.com/
    Adapt the source path to the client site's document root. Without a terminal, the cPanel file manager lets you compress the folder, move it, then extract it into the subdomain's folder.

  4. Create a dedicated database and user

    In the MySQL databases section of cPanel, create a new database and a user, and give it all privileges on that database only. Then open the copy's wp-config.php and change DB_NAME, DB_USER and DB_PASSWORD. This is the costliest mistake in this guide: copying wp-config.php without changing those three lines makes the staging site write into the production database.

  5. Import the database

    From the copy's folder:
    cd ~/staging.your-domain.com
    wp db import ~/preprod-backups/before-staging.sql
    wp option get siteurl
    The last command should still show the production address: that is normal, the URLs have not been replaced yet. If it fails, wp-config.php still points to the wrong database.

  6. Replace the URLs, as a dry run first

    wp search-replace 'https://your-domain.com' 'https://staging.your-domain.com' --all-tables --skip-columns=guid --dry-run
    Read the report: it lists the tables and the number of replacements. Then run the same command without --dry-run. WordPress stores many settings as serialized data, where each string carries its length: a sed on the export changes the URL length without fixing that count, and the settings become unreadable. wp search-replace understands this data and rewrites it correctly.

  7. Set the staging settings in wp-config.php

    Add, above the line "That's all, stop editing":
    define('WP_ENVIRONMENT_TYPE', 'staging');
    define('DISABLE_WP_CRON', true);
    define('DISALLOW_FILE_EDIT', true);
    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    Errors go to wp-content/debug.log without showing to visitors. WP_ENVIRONMENT_TYPE has existed since WordPress 5.5 and accepts local, development, staging and production: it lets themes and plugins adapt, but it does not hide the site from search engines, hence the next step.

  8. Close access and request noindex

    Under Settings › Reading, tick "Discourage search engines from indexing this site", or from the command line: wp option update blog_public 0. Then add HTTP authentication to the copy's folder: a .htpasswd file created with htpasswd -c ~/.htpasswd-staging client, then in the folder's .htaccess the directives AuthType Basic, AuthName "Preproduction", AuthUserFile pointing to that file and Require valid-user. Failing that, restrict access to an IP list.

  9. Check the copy the way a robot would

    curl -sI https://staging.your-domain.com/
    The response should be a 401. Then, with the credentials:
    curl -s -u client https://staging.your-domain.com/ | grep -i robots
    You should see a meta name='robots' tag containing noindex. If a cache or SEO plugin replaces it, that is the one to configure. Finally, check that the theme contains no hard-coded production address: grep -r "https://your-domain.com" wp-content/themes.

  10. Attach staging to your agency routine

    Turn this setup into a template: one subdomain per client, in the client's cPanel account, with the same checklist. The order of operations for moving a site to another host is covered by the guide "Migrate a client WordPress site without downtime"; the staging site described here is a lasting tool, not a cutover step.

Really isolate it: noindex is not enough

Noindex asks search engines not to keep a page; it stops nobody from reading it. A staging site open to everyone ends up in a link pasted in a ticket, in a sitemap or in a screenshot, and it then publishes content duplicated from production. The two protections complement each other: noindex tells engines not to index, HTTP authentication or an IP list forbids access.

One trap: if you block the subdomain with robots.txt, a robot no longer reads the page and therefore never sees the noindex tag. With authentication in place, that file is of no use.

Neutralise emails, payments and scheduled tasks

The copy contains the site's real customers: subscribers, buyers, order addresses. Everything that sends itself must be switched off before the slightest test, otherwise an abandoned-cart reminder, an appointment reminder or a subscription renewal goes out twice from the copy. WordPress scheduled tasks are triggered by visits: on staging, disable them with DISABLE_WP_CRON and run them by hand with wp cron event run --due-now when you want to test them.

Checklist before touching a single plugin

  • Emails — sending plugin in test mode or redirected to a review mailbox, shop notifications switched off.
  • Payments — gateway in test mode, production keys removed from the copy, callback addresses (webhooks) not declared for staging.
  • Scheduled tasks — DISABLE_WP_CRON set, pending events listed with wp cron event list.
  • File editing — DISALLOW_FILE_EDIT active so code cannot be edited from the admin.
  • Error log — WP_DEBUG_LOG active and WP_DEBUG_DISPLAY set to false.
  • Third-party integrations — CRM, directories, analytics tools and production API keys replaced or removed.
  • Search engines — noindex set and authentication in place, checked with curl as above.

Test updates, one at a time

List what is waiting for an update with wp plugin list --update=available. Then update one plugin at a time: wp plugin update plugin-name, browse the pages that matter (home, contact form, cart, login) and read the end of the log with tail -n 30 wp-content/debug.log. Move on to the next one only when the log is quiet. Do the same for the theme with wp theme update theme-name, then for core with wp core update.

In a change log, note for each line the version before and the version after. That is the list you will replay on production, version by version, instead of copying anything.

When the update breaks: reading debug.log

The classic symptom is the screen "There has been a critical error on this website". WordPress then enters recovery mode and sends a link to the admin address, which assumes email sending works: on staging it is neutralised, so do not wait for it. The cause is in the log: tail -n 40 wp-content/debug.log. Look for the last line starting with PHP Fatal error, such as Call to undefined function or Cannot declare class, and note the file path: the folder under wp-content/plugins/ names the faulty plugin. Deactivate it without going through the admin using wp plugin deactivate plugin-name, and the site comes back and you know who to blame.

Troubleshooting: the other symptoms you really meet

  • The site redirects to production — wp option get siteurl and wp option get home still show the old address, or a page builder keeps hard-coded URLs; run the replacement again, with --precise if needed.
  • "Error establishing a database connection" — the copy's DB_NAME, DB_USER, DB_PASSWORD or DB_HOST are wrong, or the user has no rights on the new database.
  • Images or styles loaded from production — escaped URLs, such as https:\/\/ in page builders' JSON data, escaped the replacement; run it again with that form.
  • Login loop or immediate logout — the cookies belong to the old domain; clear them and check siteurl and home.
  • The staging site shows up in a search engine — noindex was set too late or a plugin overrides it; switch to HTTP authentication and request removal of the address.

Files or database: what moves, and how

Scroll the table

ItemWhere it livesHow to move it to production
Theme and child themeFilesDeploy the validated folder, or replay `wp theme update` at the tested version
PluginsFilesReplay `wp plugin update plugin-name --version=X` with the tested version
Must-use plugins (mu-plugins)FilesCopy the validated file, with the old one saved alongside
Uploaded media (uploads)FilesNever push from staging to production: production keeps receiving new media
Plugin settingsDatabase (options)Redo the setting by hand on production, or `wp option update`, naming the option
Orders, forms, accounts, commentsDatabaseNever overwrite: this data lives on the production side
Tables created by an updateDatabaseLet the plugin create them by replaying the update on production

Moving to production, and being able to go back

On a transactional site, never import the staging database into production: during your tests, orders, sign-ups and form entries arrived online, and an import erases them. Replay the validated changes instead. Sequence: a fresh backup (wp db export and a files archive, as in step 1), wp maintenance-mode activate, updates in order and at the versions in the log, a check of the key pages, then wp maintenance-mode deactivate.

To go back, restore the files first: wp plugin install plugin-name --version=old --force or the archive from step 1. Restore the database only as a last resort, and only if the update changed tables: everything entered since the backup would be lost, so the export must then be only minutes old. Keep the backup until the client has approved the live site.

The hardening reflex

Once the update is validated, delete the copy of the production database left in ~/preprod-backups and on staging: a full export holds customers' addresses, orders and password hashes. Never leave WP_DEBUG on for the live site, the log can contain paths and queries.

A routine that holds at portfolio scale

A staging site is judged by its discipline: always the same setup, the same checklist, the same log. For an agency, the routine fits in a procedure template: one subdomain per client, a dedicated database, noindex plus authentication, emails switched off, updates one at a time, replay to deploy. It works all the better when each client site lives in its own account, with its own backups and separate access.

One staging site per client, on isolated infrastructure

Each client site is better off in its own cPanel account: a staging subdomain, backups depending on the plan and separate access, all from a single reseller plan and one agency space, under your brand. You keep ownership of the client relationship; the infrastructure under every site is ours.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab