How to upgrade PHP on a live revenue-critical site
A sequenced playbook for upgrading PHP on a system you can't take down. Staging parity, smoke tests, gradual rollout, rollback discipline.
The riskiest PHP upgrades are not the ones that break — they are the ones that look fine on staging and quietly drop a small percentage of traffic in production. The fix is not heroic engineering. It is sequencing.
This is the playbook we follow on systems that cannot be taken down. It assumes you already know which version you are on, which you are moving to, and what is blocking you. If you have not done that yet, start with the PHP EOL checklist.
Stage 0 — The pre-upgrade audit
Before you touch a config, the upgrade has to be scoped. Most surprises in PHP upgrades come from things outside the language: an abandoned Composer package, an extension that does not exist on the new version, a cron worker still pointing at the old binary, or a hosting image that ships PHP-FPM separately from the CLI.
The audit is the boring part. The cleaner the audit, the boring the upgrade. If the risk register feels too large to act on, our audit is the structured way to turn it into a sequenced plan.
Stage 1 — Make staging match production
A staging environment that is “close enough” to production will lie to you. Specifically, it will lie about edge cases that only show up under real data, real cron timings, and real third-party traffic.
Before any upgrade work, lock these down:
- Same OS, same package source, same PHP-FPM version
- Same database engine and version
- Same Composer lockfile, same Node version if assets are built
- Same web server config — including the
.htaccessor nginx rules - A recent, anonymized copy of production data (not a synthetic seed)
- The cron jobs and queue workers running on the same schedule
If staging skips any of these, every test you run on it is advisory at best.
Stage 2 — Smoke tests around the money flows
Do not aim for full coverage. Aim for a small set of tests that you trust around the flows that pay the bills.
The list is shorter than people expect:
- Login (with and without 2FA)
- Checkout, payment, and the success/failure callbacks
- Lead and contact forms
- Admin save actions on the most-used record types
- File uploads
- Scheduled jobs that touch billing, email, or external APIs
- Outbound email delivery to a real inbox
These tests should run on staging before any PHP version change. If they fail on the current version, you have other problems first.
Stage 3 — Upgrade staging, then lock the diff
Bump PHP on staging. Run the smoke tests. Fix what breaks.
The fixes you write here are the upgrade — and they belong in version control with the rest of the codebase, not as ad-hoc edits on the staging server. Lock the resulting diff. That diff is what ships to production. Nothing more.
For Laravel-specific systems, this is also where framework majors get layered on. We treat that as a separate sequence — see Laravel upgrade for the staged path from Laravel 6 onward.
Stage 4 — Production cutover
Pick a low-traffic window. Confirm a current backup. Confirm rollback.
Rollback should be one of three concrete actions, decided in advance:
- A host-level switch back to the previous PHP version (managed hosting)
- A Docker image revert to the previous tag (containerized stacks)
- A backup restore (when the upgrade includes a destructive change — usually a database migration, not the PHP runtime itself)
Run the upgrade. Run the smoke tests against production. Tail the error log for the first hour.
If anything that looks like a regression shows up, do not debug in production. Roll back, reproduce on staging, fix the diff, ship again.
Stage 5 — Observe, then unwind compatibility shims
Most PHP upgrades leave behind small compatibility shims: a polyfill here, a deprecated-function wrapper there, an error_reporting mask hiding a class of warnings. These are fine in the short term and corrosive in the long term.
After 7 to 14 days of clean production logs, schedule a follow-up pass to remove the shims. The system that runs cleanly on the new PHP version without compatibility props is the one you actually wanted.
What “boring upgrade” looks like
A boring PHP upgrade looks like this in the timeline:
- Week 1: audit, lockfile cleanup, abandoned package replacements
- Week 2: staging parity, smoke tests written
- Week 3: PHP bumped on staging, fixes locked in a single diff
- Week 4: production cutover, observation window
- Week 5: shims removed
That is five weeks for a system you do not want to break. Compared to the cost of a forced migration after a host drops your PHP version, or a security patch you cannot apply because the framework no longer supports it, five weeks is cheap. This is the same shape used inside legacy PHP modernization — staged, reversible, audit-first — applied specifically to the runtime.
The point is not speed. The point is that nobody on the team has to hold their breath when you ship.