On the evening of 20 October 2026, a post started circulating claiming that two critical remote code execution vulnerabilities in PHP — not in a framework or a plugin, but in the language runtime itself — would be disclosed within days. Nearly every website we run for our customers is built on PHP. This is an account of the next 48 hours: what we did, and in what order.

From rumour to official notice

The first post was unverified, so the first job was to find out whether it was real. Within hours the PHP security team published an advance notice: two critical vulnerabilities, rated CVSS v4.0 9.2, with a security release scheduled for 21 October at 14:00 UTC — moved a day earlier because details had leaked. The fixes actually landed at about 13:34 UTC. Until then nobody outside the PHP project knew which versions or configurations were affected, so we planned for the worst case: every PHP version on every server.

The first step was a complete inventory — every server, every installed PHP version, every pool that serves a site, and which package source each one updates from. In an incident like this the inventory matters more than any single fix: it tells you where a patch will arrive on its own and where it will not. One server ran an older operating system release whose package source no longer publishes updates, so rather than wait for a patch that would not come through the normal channel, we made its upgrade part of the same response.

Layered measures before the patch

With no patch yet, we added protection in layers, each cheap and reversible:

  • Disabling the suspected entry point. A researcher who reproduced the bugs pointed at PHP's session upload-progress handler. We set session.upload_progress.enabled = Off on every PHP version on every server, after checking that no site relied on it. The official advisory later confirmed that this path was exploitable on PHP-FPM — and also that it was only one of several paths, so we never treated it as a fix.
  • Looking for traces of an attack. We swept every server read-only: PHP worker crashes (a memory-corruption attempt typically crashes a worker), shells or downloaders started by PHP, changed PHP files compared against what we had deployed, new cron jobs, users and SSH keys. We found nothing.
  • Locking the origin. Most sites sit behind Cloudflare, whose emergency rules can filter attack traffic — but only if the traffic goes through Cloudflare. Anyone who knows a server's IP address can skip it. We changed the web servers so that sites behind Cloudflare only accept connections from Cloudflare and from the server itself, and moved the few sites that were not yet behind Cloudflare onto it. We wrote up how to do this in a separate guide.

The fix, and the mirror that would not serve it

When the release came out, the details were sobering: CVE-2026-109618 corrupts memory when PHP runs out of memory while parsing a request, and CVE-2026-109619 lets state from one request leak into the next on the same worker. Both allow unauthenticated code execution. Fixed versions are 8.2.35, 8.3.36, 8.4.27 and 8.5.12, with a minimal backport for 7.4. Our security advisory has the technical details.

The patched packages were built within hours, but the package mirror everyone downloads from returned HTTP 503 for most of the evening — the whole world was pulling the same update at once — and kept serving an outdated index. Rather than wait, we downloaded the already-published packages directly from the build service and installed only the packages each server already had, keeping every site on its existing PHP version. By the end of the evening every PHP version on our web servers — 7.4, 8.2, 8.3, 8.4 and 8.5 — was running a patched release, and we restarted the long-running PHP workers too, so nothing kept the old code in memory.

Upgrading the remaining server

For the server on the older release, a patch alone was not enough. We took a full, verified backup off the server, upgraded it to a supported release the same evening, and reinstalled its sites' PHP version on the new system. Before returning it to service we ran our post-upgrade checks; they found that the release upgrade had pulled in an extra web server module — the most exposed way to run PHP for this exact bug — and we removed it. This was the only part of the response our customers could see: that server's sites were offline for about 30 minutes during the upgrade. Apart from a few seconds during service restarts, every other site stayed up.

Related hardening in the same days

The same days brought two other security releases. A certificate authority's intermediate keys were found to be compromised; the certificate lists on our servers were updated, and we checked that none of our sites used that authority. Node.js shipped security releases, which we installed and restarted our Node services onto. We also bound databases and caches to the local machine only, so that they are protected by their own configuration as well as by the firewall — defence in depth should not depend on one layer. And we added process auditing, so that the question "did PHP run a command it should not have?" is answered from a log.

What we take away

A fix being released and a fix being installed are different moments, sometimes hours apart. The hours in between are where preparation pays off: knowing what you run, having cheap interim measures ready, and being able to install a patch without waiting for the usual channel. The inventory, the process auditing and the origin lock stay in place as permanent parts of our setup.