Staging Script
2026-05-26
The problem and the approach to solving it
We recently moved away from the staging system we had been using and needed a replacement.
Rather than adopting another third-party service, we decided to build our own staging solution using tools we already owned and trusted.
We already had:
WHM/cPanel
JetBackup
WP-CLI
Bash
The challenge was figuring out how to tie those pieces together into a process that was simple enough to use regularly while remaining easy to maintain.
The result was a custom staging system that allows us to create a staging environment from any available JetBackup restore point using a simple aliases for a bash script.
The SSL Problem
The hardest part of this project was not creating the staging system.
The hardest part was SSL.
Creating a staging copy of a website is fairly straightforward. Creating a staging environment that can be repeatedly created and destroyed without touching DNS, SSL certificates, virtual hosts, or cPanel every time is considerably more difficult.
I knew from the beginning that I did not want to:
Create DNS records
Issue SSL certificates
Create cPanel accounts
every time a staging site was needed. I needed a quick way to determine the specific backup to use for staging creation that didn’t require a bunch of extra work.
The solution was creating a dedicated staging cPanel account and building everything around wildcard DNS and wildcard SSL.
All staging sites live under:
*.redacted.alsoredacted.com
Examples:
client-test.redacted.alsoredacted.com
plugin-upgrade-test.redacted.alsoredacted.com
redesign-test.redacted.alsoredacted.com
Because the wildcard infrastructure already exists, every staging site immediately has a valid SSL certificate and can be accessed securely without any additional work.
This ended up being one of the most important design decisions in the entire project.
Overview of How It Works
Creating a staging site is intentionally simple.
staging CPANELUSERNAME STAGINGNAMEThe script then:
Locates the site’s JetBackup account.
Displays available backups.
Allows the user to select a restore point.
Downloads the selected backup.
Restores the website files.
Creates a new database.
Imports the database.
Reconfigures WordPress.
Removes production caching systems.
Verifies the finished environment.
Once complete, the script outputs the staging URL and the site is ready for use.
The Technicals
I’ve intentionally simplified a few things below, but everything is based on the actual implementation.
1. Start with a Simple Command
The script accepts two pieces of information:
staging SOURCE_USER STAGING_IDThe source user identifies the production site.
The staging ID becomes the subdomain, directory name, database naming seed, and general identifier used throughout the process.
This allows the same script to create unlimited staging environments without any hard coded configuration.
2. Build Around JetBackup
The backup system was already solving the hardest part of the problem.
Rather than creating another backup system, another storage system, or another restore process, the script simply asks JetBackup for available backups.
JetBackup already handles:
Backup creation
Retention
Storage
Archive generation
There was no reason to reinvent any of that.
3. Let the User Choose the Restore Point
One design decision I was very intentional about was allowing the user to choose which backup to restore.
The script presents all available backups and waits for a selection.
Available backups:
1) 2026-05-24 02:00:00
2) 2026-05-23 02:00:00
3) 2026-05-22 02:00:00
Automatically choosing the latest backup sounds convenient until you need to investigate something that happened three days ago.
Giving the user control over the restore point based on all available JetBackup backups was a main requirement.
4. Wait for JetBackup to Finish
The script queues a JetBackup download and continuously monitors the queue until the process completes.
I did not want the script guessing when a download might be finished.
Instead it waits for confirmation from JetBackup itself.
Once complete, the downloaded archive is located automatically and extracted into a temporary working directory.
5. Restore the Website Files
The website files are restored from the backup archive into the staging environment.
Permissions are corrected.
Temporary files are removed.
The staging environment now contains a complete copy of the production site’s files.
6. Create an Isolated Database
Every staging site receives:
Its own database
Its own database user
Its own password
All generated automatically.
The staging environment never depends on the production database.
7. Detect the Table Prefix Automatically
WordPress sites do not always use the standard wp_ table
prefix.
Rather than assuming anything, the script inspects the imported database and automatically determines the correct prefix.
This allows staging creation to work regardless of how the original site was configured.
8. Why Full Search-Replace Was Abandoned
One of the more interesting lessons learned during development involved URL rewriting.
The traditional recommendation is often to perform a full database search and replace after moving a WordPress site.
While that can work, it can also create problems with:
Page builders
Plugin data
Serialized content
Generated assets
After testing several sites, I became increasingly uncomfortable with performing blanket replacements across entire databases.
Instead, the script updates only the critical WordPress settings and performs targeted replacements where necessary.
This proved considerably safer. What we came up with works perfectly for 95%+ of all websites. We just ran into some weird Divi/Elementor quirks and had to adjust a little. What we came up with works great and if some more detailed manual work is required for 5% or so or less - so be it.
9. Targeted URL Rewrites
Rather than touching everything, the script focuses on specific locations known to contain generated assets.
Examples include:
Astra local fonts
Elementor generated assets
Elementor CSS directories
The goal is solving the actual problem while minimizing unnecessary changes.
10. Neutralizing Cache and Optimization Plugins
A staging environment should not inherit production optimization layers.
The script automatically removes copied instances of:
LiteSpeed Cache
WP Rocket
It also clears cache directories, disables caching, flushes transients, and rebuilds rewrite rules.
This helps ensure that troubleshooting is focused on the website itself rather than stale cache data.
The idea being that it’s easier to reinstall WP Rocket or LS Cache than create an unstable script with a lot of gymnastics to make WP Rocket and LS Cache work seamlessly through staging creation. We ran into a lot of issues. WP Rocket can be reinstalled and settings configured to match the live site in a matter of minutes. LS Cache can be installed and setting exported/imported from the live site.
11. Cleanup and Safety Mechanisms
One feature that probably does not get enough attention is cleanup.
The script contains a dedicated error trap.
If something fails halfway through the process, it automatically removes:
Partially restored files
Temporary working directories
Databases
Database users
Without that cleanup, failed staging attempts would slowly litter the server with abandoned resources.
12. Inventory and Removal Tools
As staging environments accumulated, additional helper scripts became necessary.
To view all staging sites:
staging-listThis displays:
Site name
Size
Database
URL
To remove a staging site:
staging-remove STAGINGNAMEThe removal process requires confirmation before deleting:
Files
Databases
Database users
Working directories
The script also performs safety validation to ensure it cannot accidentally remove directories outside the staging environment.
Lessons Learned
The biggest lesson from this project was that cloning WordPress is actually the easy part.
The difficult part is creating an environment that can be repeatedly created, tested, and destroyed without requiring DNS changes, SSL management, cPanel work, or manual database administration.
The wildcard SSL architecture ended up being just as important as the staging script itself.
Without it, the system would have required far too much manual work to be practical.
Conclusion
The final result is a staging system built entirely around infrastructure we already own and maintain saving the company about $12k/year by not relying on the third party system we had been using.
JetBackup handles backup creation and storage.
The staging scripts handle restoration, database creation, WordPress configuration, cleanup, and removal.
Combined with wildcard DNS and wildcard SSL, the process is fast enough to use regularly while remaining simple enough to maintain.