php2shell: Patching 66 PHP Sites, and Why 12 Others Needed Nothing

Dark Choice OMG title card. A chip in the top right reads Web Platforms. An overline reads php2shell, October 2026. The headline reads: The cheapest patch is the one you don't need. Beneath it: 12 static sites took 12,591 WordPress and PHP requests. Every one got a 404. The footer reads Digital marketing run like infrastructure on the left and choice.marketing on the right.
The cheapest patch is the one a server doesn't need.

PHP published fixes for two remote code execution bugs, nicknamed php2shell, on October 21, 2026. We patched or mitigated every PHP web server we operate that day, including 66 client sites on our main hosting server. Twelve other sites on the same server needed no work at all, because we had rebuilt them as static HTML two months earlier. In the 63 days since that rebuild, those 12 sites received 12,591 requests for WordPress and PHP files, and every one of them got a 404.

The best patch is one you don't need

On the morning php2shell was announced, 12 of the sites on that server had no PHP to patch. They had run WordPress until August 19, 2026. That day we captured each one as plain HTML, served it straight from the web server, and switched PHP off for the site entirely. The bugs PHP fixed on October 21 couldn't reach them, because nothing on those sites executes code when a request arrives.

CVE-2026-109618 corrupts memory when PHP runs out of it; CVE-2026-109619 lets a request reuse state left behind by an earlier one. At worst, together they allow code execution before a website's own software has run a single line, which is why the UK's National Cyber Security Centre issued an alert.

The August rebuild wasn't planned with php2shell in mind. It started with a review of theme licences: the premium theme on these sites had expired licences and no update path, and four of them were running releases from major versions as old as 2.3 and 3.4. A theme that can't be updated is a vulnerability with a waiting period.

So we asked one question of each site: does anyone actually edit it? For a dozen small business sites the answer was no. They were brochures that WordPress rebuilt from the database on every uncached visit, carrying a theme we couldn't update and 96 plugin folders between them, each of which needed patching on someone's schedule. A brochure doesn't need a database.

What the rebuild changed, measured

Requests stopped reaching code

Our server logs cover the eight days before the conversion and the 63 days after it. In the eight days before, leaving out WordPress's calls to its own scheduler, the 12 sites received 3,753 requests for login pages, XML-RPC, the admin area, the REST API and other PHP files, and 1,777 of them got a 200, which means PHP ran and answered. Not every one was hostile; some REST API calls came from the sites' own pages. In the 63 days after, the same sites received 12,591 such requests, most of them for PHP files that never existed on these sites, the signature of automated scanners hunting for known holes. All 12,591 got a 404. None reached PHP, because there is no PHP behind those sites.

Scanners don't stop trying, and they don't need to succeed often. Static HTML turns every attempt into a request for a file that isn't there.

Pages start arriving faster

We timed 12 static sites against 12 WordPress sites on the same server on October 21: five requests to each homepage from our office, with the encryption handshake subtracted, so the figure is the time the server took to start answering. The static sites took a median of 60 ms. WordPress took 114 ms when its page cache had a copy, and 338 ms when it had to build the page. The slow end widens the gap: one request in ten took longer than 115 ms on a static site, 276 ms on a cached WordPress page, and 692 ms on an uncached one.

Part of the 60 ms is the distance between our office and the server, which no website can remove. Most of the difference above it is PHP and the database doing work a brochure never needed.

Maintenance disappeared

The 12 sites used to carry WordPress core, an unpatchable theme and 96 plugin folders. They now carry HTML, images, stylesheets and scripts. There is nothing to update, no plugin vulnerability notice that applies to them, and no admin account for an attacker to create.

What a static site costs

A static site gives up the editing screen, and that is the trade a client has to agree to. A content change means rebuilding the page and redeploying it. We do that for clients, but it's slower than logging in and typing, which is why the 12 conversions were all sites nobody had edited in a long time.

Forms have to be rebuilt too. WordPress form plugins process submissions in PHP, so on a converted site a form becomes either a phone and email call to action or a standalone form that posts to a separate handler. Interactive pieces can break in ways a link checker won't see. On one rebuild, a client told us that a gallery wouldn't go to its second page and a button did nothing, because both depended on scripts that expected WordPress behind them. We fixed both the same day, and we now click through every popup and pager in a real browser before calling a rebuild done.

Static HTML also doesn't make a heavy page light. On a hybrid site where we made only the homepage static, Google's PageSpeed lab score was 55 the week before the change and 53 the week after. The page still loaded the same scripts, fonts, video and tracking tags, and those dominate a mobile lab test. Static removes server time. Page weight is a separate job.

The middle ground: static where it counts

Not every site can give up WordPress, so several of our rebuilds are mixtures. A healthcare client's homepage is now a static file, while WordPress keeps serving and editing every other page. A renewable energy client's public site is a static copy on a separate host, and the old WordPress install survives only as a private, password-protected source we capture new pages from. The public never reaches it.

Since July the rebuilds have run the full range, decided page by page: 12 brochure sites converted in place, a site moved off a hosted website builder onto plain HTML, the two hybrids, and our own hosting site built static from the start. Two more are in progress, including the one php2shell put at the front of the queue.

The rest of the PHP: patching day

Static sites only help where they've been built. Everywhere else, we patched. We swept the 22 Linux hosts we run for PHP; two staging machines didn't answer and are still on the list.

On the rest, PHP was answering web requests in four places: the hosting server's 66 PHP sites, a client's WooCommerce store on its own server, our staff webmail, and a development stack on a desk machine, which is now stopped. Before installing anything we applied PHP's interim advice, turning off the session upload-progress feature through which FastCGI servers are exposed. The fixed releases were PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.

The hosting server then reported that no fix existed, and it was wrong. The server runs Plesk, which ships its own PHP builds. Plesk had started signing its PHP 8.4 repository with a new key, created on August 18, and the server held only the old one. The package manager couldn't verify the repository, so it kept reporting the last list it trusted, which made "already up to date" look like the right answer. Once we installed Plesk's current keyring, PHP 8.4.27 appeared, built that same morning, along with fixes for 8.2 and 8.3.

We upgraded one version at a time and stopped if any upgrade wanted to remove a package. After each one we compared the status code of all 66 homepages against a baseline taken before we started, and they matched every time.

Plesk's same-day rebuild of PHP 8.1 came with a changelog that said only "Package created with FPM", which doesn't say whether the fix is inside. Three sites still ran 8.1, so we moved them to 8.3.36 and checked every page in their sitemaps, their error logs, and that each WordPress install loaded with all of its active plugins.

Two other places needed different routes. The WooCommerce store took 36 package upgrades, after a first attempt failed when the package host returned "503 Service Unavailable" on 16 of them.

Our webmail runs in a container built on Alpine Linux 3.20, which had no fixed PHP. Alpine 3.21, 3.22 and 3.23 still didn't, and 3.24.2 did. We rebuilt the image on 3.24.2, tested it beside the live one, and swapped it in with the old image held for rollback.

One client's site runs on another company's hosting, on PHP 7.4.33, a version that stopped receiving official fixes years ago, on a server we can't touch. That one is becoming the next static rebuild. The copy is on a staging address while DNS and form delivery are settled, and of 348 full-page comparisons against the live site, across 87 pages at four screen sizes, 343 are pixel-identical.

What stays

The upload-progress setting stays off everywhere after patching, as PHP recommends for anyone who doesn't use the feature. Every site on the hosting server has stopped announcing its PHP version in its response headers.

We didn't apply PHP's other interim mitigation, removing the memory limit. WordPress resets the limit itself, and unbounded PHP workers are a bigger risk on a shared server than the bug that mitigation guards against.

And the question we asked of the 12 brochure sites in August now goes to every site we host: if nobody edits it, why is it running code? Patching 66 sites took a day. The 12 that had nothing to patch took no time at all, and they'll take none the next time PHP ships an emergency fix.

Need Help With Your Digital Marketing?

Request a free discovery call with our team.

Get in Touch