Before you switch PHP versions on a WordPress site: checks, a staging clone, and a rollback that survives a failed upgrade
The host panel gives you a dropdown of PHP versions. Changing it takes one click. Finding out whether the site still works takes a morning, and if it doesn't, you're doing it in front of customers. Most of the prep work is finding out what is actually running, and making sure that undoing the switch restores data as well as files. Here is the sequence I'd run, in order. Start with an inventory, including the parts that have no headers cd /path/to/wordpress wp core version wp eval 'echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;' wp plugin list --fields=name,status,version --format=table wp theme list --fields=name,status,version --format=table wp plugin list --status=must-use --fields=name --format=csv ls -1 wp-content/*.php 2>/dev/null Save the list so you can diff against it after the switch: { wp plugin list --fields=name,status,version --format=csv wp theme list --fields=name,status,version --format=csv wp plugin list --status=must-use --fields=name --format=
The host panel gives you a dropdown of PHP versions. Changing it takes one click. Finding out whether the site still works takes a morning, and if it doesn't, you're doing it in front of customers.
Most of the prep work is finding out what is actually running, and making sure that undoing the switch restores data as well as files. Here is the sequence I'd run, in order.
Start with an inventory, including the parts that have no headers
cd /path/to/wordpress
wp core version
wp eval 'echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;'
wp plugin list --fields=name,status,version --format=table
wp theme list --fields=name,status,version --format=table
wp plugin list --status=must-use --fields=name --format=csv
ls -1 wp-content/*.php 2>/dev/null
Save the list so you can diff against it after the switch:
{ wp plugin list --fields=name,status,version --format=csv
wp theme list --fields=name,status,version --format=csv
wp plugin list --status=must-use --fields=name --format=csv
} > ~/inventory-before-$(date +%F).csv
The ls at the end matters more than people expect. object-cache.php, advanced-cache.php, db.php and sunrise.php are drop-ins: PHP files WordPress loads directly, with no plugin header, no update mechanism and no compatibility statement anywhere. An object cache drop-in built for an older PHP version is a reliable source of white screens.
Then check whether the CLI and the web process are even the same version. wp eval 'echo PHP_VERSION;' runs on the PHP CLI binary. Visitors hit PHP-FPM (or whatever your host runs). If the two differ, every WP-CLI command you run is testing a runtime your visitors never touch. Site Health reports the web-process version under Tools → Site Health → Info → Server; compare it with the CLI output before you trust any of this.
Static scan for code the new version removed
PHPCompatibility run through PHP_CodeSniffer catches removed functions, removed ini directives, changed signatures and deprecated constructs.
mkdir -p ~/phpcompat && cd ~/phpcompat
composer require --dev squizlabs/php_codesniffer phpcompatibility/php-compatibility
vendor/bin/phpcs --config-set installed_paths "$PWD/vendor/phpcompatibility/php-compatibility"
Then point it at the plugins and themes directory. Swap 8.3- for the version you're moving to.
cd ~/phpcompat
vendor/bin/phpcs -p \
--standard=PHPCompatibility \
--extensions=php \
--ignore='*/vendor/*,*/node_modules/*,*/tests/*' \
--runtime-set testVersion 8.3- \
/var/www/example.com/current/wp-content/plugins \
/var/www/example.com/current/wp-content/themes
If the report is unreadable, run it one plugin directory at a time so you know which one owns the error. What it can't tell you: behaviour changes, extensions missing from the new build, and anything generated at runtime or shipped minified. Treat a clean report as permission to test, not permission to ship.
A quick sanity pass on declared requirements — knowing it's a declaration and not a guarantee:
grep -rhiE '^[[:space:]]*\*?[[:space:]]*Requires PHP:' wp-content/plugins wp-content/themes \
| sed 's/.*Requires PHP:[[:space:]]*//' | sort | uniq -c | sort -rn
Compare the extension sets
A version switch on the same host often comes with a different compiled extension list. Missing intl, gd, imagick, mbstring, zip, curl or soap breaks sites in ways no code scan will predict.
# on an environment already running the target version
php -m | sort > /tmp/ext-new.txt
# on the current production version
php -m | sort > /tmp/ext-old.txt
diff /tmp/ext-old.txt /tmp/ext-new.txt
The CLI list is a proxy. If your host exposes per-pool configuration, ask which extensions are enabled for the FPM pool that serves the site — that's the list that matters.
Build the staging clone
# 1) dump production
cd /var/www/example.com/current
wp db export ~/prod-$(date +%F-%H%M).sql
# 2) copy files, skipping caches but keeping uploads
rsync -a \
--exclude='wp-content/cache' \
--exclude='wp-content/uploads/cache' \
/var/www/example.com/current/ /var/www/staging.example.com/current/
# 3) import into the staging database
cd /var/www/staging.example.com/current
wp db import ~/prod-$(date +%F-%H%M).sql
# 4) repoint the clone, serialized data included
wp search-replace 'https://example.com' 'https://staging.example.com' \
--all-tables --precise --recurse-objects --skip-columns=guid --report-changed-only
# 5) keep it out of search and out of production caches
wp option update blog_public 0
wp config set WP_ENVIRONMENT_TYPE staging
wp config set WP_DEBUG true --raw
wp config set WP_DEBUG_LOG true --raw
wp config set WP_DEBUG_DISPLAY false --raw
wp cache flush
Two things that bite here.
If wp-content/object-cache.php exists, staging and production may now be sharing a Redis or Memcached namespace. Delete the drop-in on staging or change its prefix, otherwise you'll spend an hour debugging cache entries that belong to the other site.
And --all-tables means all tables in that database. If the database is shared, run wp db query "SHOW TABLES" first and confirm what you're about to rewrite.
Put HTTP Basic Auth in front of staging at the web server level before you turn on WP_DEBUG_LOG, and check the log isn't web-readable.
Switch PHP on staging only
Most hosts let you set the PHP version per domain or subdomain. Change it for staging and leave production alone.
wp eval 'echo PHP_VERSION, PHP_EOL;'
wp cron event run --due-now
curl -sS -o /dev/null -w '%{http_code}\n' https://staging.example.com/wp-json/
tail -f wp-content/debug.log
Then click through, roughly in this order: log in, edit and save a post, upload an image, submit your contact form, place a test order if you take orders, and open one page that uses each builder or shortcode you have. Keep an eye on debug.log and the PHP error log while you do it.
Running wp cron event run --due-now is worth doing specifically. Scheduled work goes through the CLI or a loopback request and touches code paths the page render never reaches, so it's where "it worked when I clicked it" sites fall over.
Write the rollback down before you need it
Switching the PHP version back is not a rollback. Once something has migrated data on the new version — a core schema update, a plugin's table changes, a serialized option rewritten — restoring the files gives you a half-upgraded site. Files and database have to come back together.
cd /var/www/example.com/current
wp db export ~/pre-cutover-$(date +%F-%H%M).sql
tar czf ~/pre-cutover-files-$(date +%F).tar.gz -C /var/www/example.com .
Then leave a note where you'll find it at 2am, containing:
- which PHP versions the host will still let you switch back to (some drop old ones after a migration window — find out now, not later)
- the exact paths of the dump and the tarball
- the order of operations: maintenance mode on, restore files, import the dump, switch PHP back, flush caches, maintenance mode off, load the homepage and the admin
wp maintenance-mode activate
# restore files, import the pre-cutover dump, switch the version back
wp cache flush
wp maintenance-mode deactivate
Cutover
Lowest-traffic window you can get. Switch the version in the panel, flush caches, then load the homepage, the login page and one page behind the login. Watch the error log for a few minutes rather than a few seconds.
Keep the old PHP version selectable for a couple of weeks, and keep the pre-cutover dump longer than feels necessary.
The parts people skip are the drop-ins, the extension diff, and the database half of the rollback. Those are also the parts that turn a five-minute switch into an afternoon.
If you'd rather not run this yourself, WordPress work like this is what we quote for: https://aumcreate.com/services?utm_source=devto&utm_medium=content&utm_campaign=devto-php-upgrade-compat-rollback