WordPress Staging on Ubuntu 24.04 with Safe Rollback

Why this staging setup matters
WordPress staging on Ubuntu 24.04 gives you a separate copy of a live site, so you can test plugin updates, theme changes, and checkout flows before customers ever see them. This tutorial uses a fresh Ubuntu Server VPS, Nginx, PHP-FPM, and MariaDB, then adds a sync-and-rollback workflow you can rely on during a launch window or busy sales period.
If you manage WordPress sites for clients, this kind of change control helps you avoid preventable incidents. It also fits well with Hostperl VPS hosting for staging and launch work, especially when you need a dependable test environment before pushing changes to production: Hostperl VPS.
What you will build
You will create:
- a non-root admin user with sudo access
- an isolated staging domain on Ubuntu 24.04
- Nginx, PHP-FPM, and MariaDB for WordPress
- a staging database and file copy from production
- basic HTTP authentication so search engines and customers do not find the staging site
- a rollback plan that restores the previous staging snapshot if a test breaks
This is not a one-command install. The goal is a staging site that behaves like production and still gives you room to test safely. If you want a broader release workflow, pair this with Hostperl’s WordPress staging guidance and cutover planning, including WordPress staging and rollback for safer releases and WordPress staging and migration planning for safer releases.
Before you start
Run everything on a fresh Ubuntu Server 24.04 VPS. If you are using a different Ubuntu release, adjust package names only if your repository sources require it. The commands below assume the server IP example 203.0.113.10. Replace it with the real public IP assigned to your server.
On your local computer
ssh root@203.0.113.10203.0.113.10 is a documentation address. Replace it with your server’s real IP before connecting.
On the VPS as root
cat /etc/os-releaseYou should see Ubuntu 24.04 details in the output. If you do not, stop and confirm the server image first.
Create the non-root admin user
Keep your root SSH session open until the new login works. That avoids lockout if you mistype a permission or firewall rule.
On the VPS as root
adduser deploySet a strong password when prompted. The new account will be your everyday admin user.
usermod -aG sudo deployThis grants sudo access through Ubuntu’s sudo group.
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysThis copies your existing SSH key access to the new user. If you do not use key-based SSH yet, add your public key before moving on.
On your local computer
ssh deploy@203.0.113.10Open a second terminal and log in as deploy. Replace the example IP with your real one. Keep the root session open until this works.
On the VPS as the non-root sudo user
sudo -l
pwdYou should see sudo rights and a normal home directory path. After this succeeds, you can stop using root for routine work.
Update the server and install WordPress packages
Start with a clean package state so you do not debug old repository metadata during a release window.
On the VPS as the non-root sudo user
sudo apt update
sudo apt -y upgrade
sudo apt -y install nginx mariadb-server php-fpm php-cli php-mysql php-curl php-gd php-xml php-mbstring php-zip php-imagick unzip wget rsync ufwThis installs the web stack, MariaDB, transfer tools, and the firewall. On Ubuntu 24.04, PHP-FPM usually ships as PHP 8.3.
php -v
nginx -v
mariadb --versionConfirm the packages installed correctly before you write config files.
Prepare the database for staging
Use a separate database and user for staging. Never point staging at the production database unless you are doing a very controlled read-only test.
On the VPS as the non-root sudo user
sudo mysqlYou will enter the MariaDB shell.
CREATE DATABASE wp_staging CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpstage'@'localhost' IDENTIFIED BY 'Use-A-Long-Random-Password-Here';
GRANT ALL PRIVILEGES ON wp_staging.* TO 'wpstage'@'localhost';
FLUSH PRIVILEGES;
EXIT;Replace the password with a unique secret. Do not reuse the production database password.
On the VPS as the non-root sudo user
sudo mysql -e "SHOW DATABASES LIKE 'wp_staging';"You should see the new database listed.
Create the staging site directory and download WordPress
Use a dedicated document root so the staging site stays separate from production files.
On the VPS as the non-root sudo user
sudo mkdir -p /var/www/staging.example.com
sudo chown -R www-data:www-data /var/www/staging.example.com
cd /var/www/staging.example.com
wget https://wordpress.org/latest.tar.gz
sudo tar -xzf latest.tar.gz --strip-components=1
rm latest.tar.gzThis creates the staging tree under /var/www/staging.example.com. Replace the hostname in the directory name with your own staging subdomain if needed.
Restore production content into staging safely
Use the production database dump and file copy only after you have a maintenance window or a recent backup. A staging clone is useful only if it mirrors real content.
On the production server or backup source
mysqldump --single-transaction --quick --routines --triggers wordpress_prod > wordpress_prod.sql
rsync -a --delete /var/www/example.com/wp-content/ /backup/wp-content-staging/Adjust the paths and database name to match your live site. Store the dump and files securely until transfer.
On the VPS as the non-root sudo user
sudo cp /path/to/wordpress_prod.sql /var/www/staging.example.com/
sudo rsync -a /path/to/wp-content-staging/ /var/www/staging.example.com/wp-content/Replace the paths with the actual transfer location you used. Do not expose the dump over the web.
On the VPS as the non-root sudo user
cd /var/www/staging.example.com
sudo mysql wp_staging < wordpress_prod.sqlThis imports the live content into staging. If the database is large, expect this to take a few minutes.
Configure WordPress for staging
Edit wp-config.php so staging uses its own database and does not send traffic to the live site by mistake.
On the VPS as the non-root sudo user
cd /var/www/staging.example.com
sudo cp wp-config-sample.php wp-config.php
sudo nano wp-config.phpAdd or replace the database values with the following block:
define( 'DB_NAME', 'wp_staging' );
define( 'DB_USER', 'wpstage' );
define( 'DB_PASSWORD', 'Use-A-Long-Random-Password-Here' );
define( 'DB_HOST', 'localhost' );
define( 'WP_ENVIRONMENT_TYPE', 'staging' );
define( 'WP_HOME', 'https://staging.example.com' );
define( 'WP_SITEURL', 'https://staging.example.com' );
define( 'DISALLOW_FILE_EDIT', true );Save and exit nano with Ctrl+O, Enter, then Ctrl+X.
On the VPS as the non-root sudo user
sudo chown -R www-data:www-data /var/www/staging.example.com
sudo find /var/www/staging.example.com -type d -exec chmod 755 {} \;
sudo find /var/www/staging.example.com -type f -exec chmod 644 {} \;This keeps ownership consistent and avoids overly permissive file modes.
Set up Nginx and PHP-FPM
Ubuntu 24.04 uses PHP-FPM sockets rather than a remote PHP daemon. That keeps the web server and runtime local, which makes WordPress staging easier to debug.
On the VPS as the non-root sudo user
sudo nano /etc/nginx/sites-available/staging.example.comPaste this full server block and replace staging.example.com with your staging hostname if different:
server {
listen 80;
server_name staging.example.com;
root /var/www/staging.example.com;
index index.php index.html;
access_log /var/log/nginx/staging.example.com.access.log;
error_log /var/log/nginx/staging.example.com.error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
}
location ~ /(\.ht|\.git|wp-config\.php) {
deny all;
}
}Save and exit, then enable the site:
sudo ln -s /etc/nginx/sites-available/staging.example.com /etc/nginx/sites-enabled/
sudo nginx -tThe syntax test must pass before you reload Nginx. If it fails, fix the referenced line first.
sudo systemctl reload nginx
sudo systemctl enable --now php8.3-fpm mariadb nginxAfter this, Nginx should serve the staging host and PHP-FPM should start at boot.
Restrict access before going public
A staging site should not be indexed or casually browsed. Add firewall rules and basic HTTP authentication before you open the site externally.
On the VPS as the non-root sudo user
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status verboseUbuntu’s UFW should show SSH and web traffic allowed, with the firewall active.
Now add password protection:
sudo apt -y install apache2-utils
sudo htpasswd -c /etc/nginx/.staging-htpasswd deployUse a strong password when prompted. This file protects the staging site from casual access.
Edit the Nginx site again and add these lines inside the server block, near the top:
auth_basic "Staging Only";
auth_basic_user_file /etc/nginx/.staging-htpasswd;
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;Then run:
sudo nginx -t
sudo systemctl reload nginxThis sequence locks down the staging site before anyone can reach it without credentials.
Finish the WordPress install in the browser
Point your staging DNS record at the VPS, then open the staging hostname in a browser. You should see the WordPress setup screen after HTTP auth. Complete the site title, admin account, and password.
If DNS is not ready yet, you can test locally by adding a temporary hosts entry on your computer that maps staging.example.com to 203.0.113.10. Remove that entry after DNS propagates.
Confirm the site is healthy
Run both server-side and client-side checks. A staging deployment is only useful if you can prove the server responds correctly and WordPress can load pages.
On the VPS as the non-root sudo user
sudo systemctl status nginx --no-pager
sudo systemctl status php8.3-fpm --no-pager
sudo ss -tulpn | grep -E ':80|:443'You should see active services and listeners on web ports. If Nginx is not listening, review the site file and the syntax test output.
On your local computer
curl -I http://staging.example.com
curl -I -u deploy https://staging.example.comThe first command should show a response, usually a redirect or auth challenge. The second should return headers from the protected staging site. Replace the domain with your real staging hostname.
On the VPS as the non-root sudo user
tail -n 50 /var/log/nginx/staging.example.com.error.log
journalctl -u php8.3-fpm -n 50 --no-pagerUse these logs if the page loads slowly, returns 502 errors, or fails to reach PHP-FPM.
Safe rollback when a test breaks
Staging should make recovery easier, not harder. Before risky plugin or theme tests, take a file and database snapshot.
On the VPS as the non-root sudo user
mkdir -p /home/deploy/staging-backups/$(date +%F)
mysqldump --single-transaction wp_staging > /home/deploy/staging-backups/$(date +%F)/wp_staging.sql
rsync -a /var/www/staging.example.com/ /home/deploy/staging-backups/$(date +%F)/files/This creates a dated backup you can restore quickly if a plugin update breaks the site.
To roll back:
sudo rsync -a --delete /home/deploy/staging-backups/2026-01-01/files/ /var/www/staging.example.com/
sudo mysql wp_staging < /home/deploy/staging-backups/2026-01-01/wp_staging.sql
sudo systemctl reload nginxReplace the dated directory with the snapshot you actually created. Restore the database first if the failure is data-related.
Troubleshooting the most likely failures
502 Bad Gateway from Nginx
Run:
sudo tail -n 50 /var/log/nginx/staging.example.com.error.log
sudo systemctl status php8.3-fpm --no-pagerIf the log mentions connect() to unix:/run/php/php8.3-fpm.sock failed, PHP-FPM is down or the socket name is wrong. Fix the socket path in the Nginx config, then reload Nginx.
Permission denied on uploads
Run:
sudo find /var/www/staging.example.com/wp-content -maxdepth 2 -type d -ls
sudo chown -R www-data:www-data /var/www/staging.example.com/wp-contentWordPress needs write access to wp-content for uploads, cache files, and some plugin operations.
White screen after plugin activation
Run:
wp plugin deactivate --all --path=/var/www/staging.example.comIf you use WP-CLI, this quickly isolates the problem plugin. If WP-CLI is not installed, rename the plugin directory in wp-content/plugins and refresh the site.
Staging site is indexed
Run:
curl -I https://staging.example.comYou should see the X-Robots-Tag header. If it is missing, confirm the header line is inside the Nginx server block and reload Nginx after a syntax test.
Reboot and persistence check
One reboot test catches a lot of missed service issues. It is better to find them now than during a client review.
On the VPS as the non-root sudo user
sudo rebootReconnect after the server comes back.
On your local computer
ssh deploy@203.0.113.10Then check service persistence again:
sudo systemctl status nginx --no-pager
sudo systemctl status php8.3-fpm --no-pager
sudo systemctl status mariadb --no-pagerAll three services should start automatically after reboot.
If you are preparing a client launch, staging on a clean Ubuntu VPS gives you a safer rehearsal space for plugin updates, checkout tests, and rollback drills. Hostperl VPS hosting fits that workflow well, and you can pair it with managed VPS hosting when you want a support-backed environment for release windows.
If you need a wider cutover process for a team handoff, Hostperl’s managed hosting cutover checklist for agency clients is a useful next step.
FAQ
Should staging use the same plugins as production?
Usually yes, because you want to test the same stack. Remove anything that sends external email, payment webhooks, or live API calls unless you have safe test credentials.
Can I use staging for checkout tests?
Yes, but only with sandbox payment settings and test email delivery. Never let staging place live orders or trigger real invoices.
Do I need a separate domain for staging?
Yes. A separate subdomain like staging.example.com keeps DNS, SSL, and access control cleaner than a path-based setup.
What is the safest way to refresh staging from production?
Take a database dump and wp-content backup, restore them into the staging database and directory, then re-run the site URL checks before opening it to testers.
How do I stop search engines from indexing staging?
Use HTTP basic auth, add the X-Robots-Tag: noindex header, and keep the staging hostname out of public links and sitemaps.
