How I Upgraded 30 WordPress Sites from PHP 8.1 to 8.3 Without Breaking a Single One

· 9 min read

The Clock Was Ticking

PHP 8.1 reached end of life on 31 December 2025. No more security patches, no more bug fixes. Hosting providers started sending increasingly urgent emails about forced upgrades in early 2026 — some gave 30 days notice, others just switched.

I had 30 client sites still running PHP 8.1 across four servers. Some were simple brochure sites. Others were WooCommerce stores processing hundreds of orders a day. Testing each one manually — clicking through pages in a browser, hoping nothing looked wrong — was not an option.

I needed a systematic approach. Here is what I built, and the three breakages that no compatibility scanner caught.

Step 1: The Audit

First, I needed to know exactly what I was dealing with. A WP-CLI loop across all sites on each server gave me the full picture in seconds:

for site in /var/www/*/; do
  echo "=== $(basename "$site") ==="
  wp --path="$site/htdocs" core version 2>/dev/null
  wp --path="$site/htdocs" plugin list --fields=name,version,update --format=table 2>/dev/null
  echo ""
done

I piped the output to a file and immediately spotted trouble: six sites had plugins that had not been updated in over two years. Those were my high-risk targets.

Step 2: Automated Compatibility Scanning

The PHP Compatibility Checker plugin works fine for a single site, but running it through the WordPress admin across 30 sites is painful. Instead, I used phpcs with the PHPCompatibilityWP ruleset directly from the command line.

Installation

composer global require phpcompatibility/phpcompatibility-wp:"*"

Batch scanning

for site in /var/www/*/; do
  echo "=== $(basename "$site") ==="
  phpcs -p "$site/htdocs/wp-content/plugins" \
    --standard=PHPCompatibilityWP \
    --extensions=php \
    --runtime-set testVersion 8.3 \
    --report=summary \
    2>/dev/null
  echo ""
done

The --report=summary flag is important — it gives you a per-file error and warning count instead of drowning you in individual notices. When a plugin flags up, drill into it:

phpcs "$site/htdocs/wp-content/plugins/problem-plugin" \
  --standard=PHPCompatibilityWP \
  --extensions=php \
  --runtime-set testVersion 8.3

Across 30 sites the scanner found issues in 14 plugins. Most were deprecation warnings that would not cause immediate breakage. But three were genuine problems that needed fixing before the upgrade.

The Three Breakages I Did Not Expect

1. utf8_encode() in an Abandoned Plugin

One site used a plugin that called utf8_encode() to parse data from a remote API response. PHP 8.2 deprecated both utf8_encode() and utf8_decode() — they will be removed entirely in a future PHP version.

The scanner flagged it. The plugin had not been updated in 18 months and the developer had gone quiet. The fix was a one-line change in the plugin file:

// Before (deprecated in PHP 8.2+)
$data = json_decode(utf8_encode($result), true);

// After
$data = json_decode(mb_convert_encoding($result, 'UTF-8', 'ISO-8859-1'), true);

Since the plugin was effectively abandoned, editing it directly was acceptable — there was no future update that would overwrite my fix. I added a comment with the date and reason so future me (or any developer inheriting the site) would understand why.

For plugins that are still maintained but slow to patch, a safer approach is to fork the plugin into a private repository and track your changes there. That way you can merge upstream updates when they eventually land.

2. Dynamic Properties Flooding the Error Log

Four WooCommerce sites used a shipping calculator plugin that set undeclared class properties throughout its codebase. PHP 8.2 emits a E_DEPRECATED notice for every dynamic property access. On a store handling 200+ orders a day, this generated over 50,000 deprecation notices daily. The error log hit 2GB within a week.

The notices do not break functionality — dynamic properties become fatal errors only in PHP 9.0. But an uncontrolled error log fills the disk, slows down log rotation, and makes it impossible to spot real errors.

I took a two-phase approach:

Phase 1 — Immediate (day of upgrade): Suppress deprecation notices only, keeping errors and warnings visible:

// wp-config.php — temporary measure
error_reporting(E_ALL & ~E_DEPRECATED);

Phase 2 — Next maintenance window: Replace the plugin with a maintained alternative that declared its properties properly. This took more time but was the right long-term fix for production WooCommerce stores.

3. Loose String Comparison in a Custom Plugin

One site had a bespoke pricing plugin written by a previous developer. The phpcs scanner flagged a pattern that revealed this logic:

if ($discount_code == 0) {
    // "No discount" branch
}

PHP 8.0 changed how loose comparisons between strings and integers work. Before PHP 8.0, "SUMMER20" == 0 evaluated to true — meaning any non-empty discount code would hit the "no discount" branch. The site was already on PHP 8.1 so this particular line was already behaving correctly, but the scanner flagged a related comparison in another function that would cause incorrect pricing logic on PHP 8.3.

The fix was strict comparison:

if ($discount_code === '' || $discount_code === null) {
    // "No discount" branch
}

This is exactly why automated scanning is not enough on its own. You need to read the code and understand what it is actually trying to do.

The Rollout

With all issues patched on staging, I upgraded the sites in batches of five over three days. The process for each batch:

1. Staging smoke test

wp --path=/var/www/example/staging/htdocs eval 'echo phpversion();'
curl -sI https://staging.example.com | head -5

2. Switch PHP on production

Update the PHP-FPM pool socket in the nginx vhost, then reload:

# In the nginx site config, change:
#   fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
# to:
#   fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;

nginx -t && systemctl reload nginx

3. Clear the OPcache

wp --path=/var/www/example/htdocs eval 'if (function_exists("opcache_reset")) { opcache_reset(); echo "OPcache cleared\n"; }'

4. Verify

wp --path=/var/www/example/htdocs eval 'echo "PHP " . phpversion() . "\n";'
wp --path=/var/www/example/htdocs cron test

Post-Upgrade Monitoring

For 48 hours after each batch I watched the PHP error logs:

tail -f /var/log/php/example-error.log | grep -E "Fatal|Warning|Parse"

Zero fatal errors across all 30 sites. A handful of deprecation notices from themes using dynamic properties — I logged those as tickets for the next maintenance cycle.

What I Learned

Abandoned plugins are the real risk. WordPress core and major plugins like WooCommerce handle PHP upgrades well. It is the niche plugins last updated in 2023 that cause problems. Before any PHP upgrade, check the "Last updated" date on every installed plugin and flag anything older than 12 months.

Do not trust GUI compatibility checkers alone. The phpcs command-line scanner with PHPCompatibilityWP is faster, scriptable, and catches issues the WordPress admin plugin misses. Once you have it set up, scanning 30 sites takes under an hour.

Batch the rollout. Five sites at a time meant that if something went wrong I could roll back quickly. On a server running CloudPanel or cPanel, switching back to PHP 8.1 takes under a minute.

Monitor the error log, not just the front end. A site can look perfectly fine in a browser while silently generating thousands of deprecation notices that fill your disk and hide real errors.

PHP 8.1 is dead. If your sites are still running it, your host will force the switch soon — and you will have no control over the timing. Better to do it on your terms, with testing, than to wake up to a broken store on a Monday morning.


Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for UK agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans → Get in Touch →

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for UK agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs