
At 14:00 UTC on 21 October 2026 the PHP project is shipping fixes for two critical flaws, and only four branches will get them: 8.2, 8.3, 8.4 and 8.5. If a server of yours still answers with PHP 8.1, 8.0, 7.4 or anything from the 5.x line, that branch has reached PHP end of life and nobody upstream is writing a patch for it. Researchers have nicknamed the pair php2shell. This guide is about what you do next: read the real support dates, understand what "no fix" means in practice, buy yourself time safely, decide whether paid extended support makes sense, and move the code to a supported branch with a workflow we ran in Docker today.
Our companion guides cover the news, version checks and web shell detection; this one is for the servers still stuck on an old branch after that check.
PHP end of life dates for every branch
PHP's policy, as written on the supported versions page, is simple: two years of active support from a branch's first stable release, then two more years of fixes for critical security issues only, released "on an as-needed basis". After four years the branch is end of life. Here are the PHP end of life dates and the last release of each branch, from that page and the PHP EOL versions list as of 21 October 2026.
| Branch | Status today | Security fixes until / EOL date | Last or current release |
|---|---|---|---|
| 8.5 | Active support (to 31 Dec 2027) | 31 Dec 2029 | 8.5.11 |
| 8.4 | Active support (to 31 Dec 2026) | 31 Dec 2028 | 8.4.26 |
| 8.3 | Security fixes only | 31 Dec 2027 | 8.3.35 |
| 8.2 | Security fixes only | 31 Dec 2026 | 8.2.34 |
| 8.1 | End of life | 31 Dec 2025 | 8.1.34 |
| 8.0 | End of life | 26 Nov 2023 | 8.0.30 |
| 7.4 | End of life | 28 Nov 2022 | 7.4.33 |
| 7.3 / 7.2 | End of life | 6 Dec 2021 / 30 Nov 2020 | 7.3.33 / 7.2.34 |
| 7.1 / 7.0 | End of life | 1 Dec 2019 / 10 Jan 2019 | 7.1.33 / 7.0.33 |
| 5.6 | End of life | 31 Dec 2018 | 5.6.40 |
| 5.5 / 5.4 / 5.3 | End of life | 21 Jul 2016 / 3 Sep 2015 / 14 Aug 2014 | 5.5.38 / 5.4.45 / 5.3.29 |
Two things stand out. PHP 8.1 end of life was less than ten months ago, so teams that "upgraded recently" may already be unsupported. And end of life for PHP 8.2 is ten weeks away: it gets today's fix, but if you are migrating anyway, aim higher. PHP 8.6.0 is still a release candidate that php.net says not to run in production. PHP 7.4 end of life was 28 November 2022; 7.4.33 is its last build.
What "no fix" means when a critical bug lands
PHP's advance notice of 20 October says the release addresses two critical vulnerabilities, both rated 9.2 on CVSS v4.0, and promises the details, the list of affected branches and the fixed version numbers only when the release ships. When we last checked php.net, at 11:36 UTC on 21 October, the release was not out. So the claim circulating since the leak, that every version from PHP 5 onward is affected, is still unconfirmed. The CVE numbers researchers use (CVE-2026-109618 and CVE-2026-109619) also come from the leak, not from PHP. CERT-EU's advisory repeats the "PHP 5 onwards" scope as something the flaws "could" affect, citing a social media post.
The schedule already tells you the consequence: if the bugs reach 8.1 or older, php.net will publish no patched build for those branches. There is precedent. When CVE-2024-4577 (PHP-CGI argument injection on Windows) was fixed, the fixed versions were 8.1.29, 8.2.20 and 8.3.8, the branches supported at the time. CISA added it to its Known Exploited Vulnerabilities catalog on 12 June 2024. Anyone on 8.0 or 7.x got nothing.
How many servers are in this position
The security vendor Mallory ran Shodan queries on 20 October and reports that 883,717 (59%) of the roughly 1.5 million internet-facing hosts showing a PHP version in their X-Powered-By header run 8.1 or older. By its count, PHP 5.6 (187,154 hosts) and 7.2 (184,302) are the biggest groups, with 8.1 at 106,834 and 7.4 at 68,895. Treat that as a third-party estimate from banners: it misses the roughly 740,000 PHP hosts Mallory says hide their version, and it cannot see backported patches, which is the next point.
Your distro's PHP is not the same as php.net's PHP
A version string tells you the upstream branch, not the patch level. Ubuntu 22.04 ships PHP 8.1, and its package 8.1.2-1ubuntu2.27 was published to the jammy security pocket on 19 October 2026, per Launchpad. Running php -v on that box prints 8.1.2, far behind php.net's final 8.1.34, yet Ubuntu keeps publishing security updates for that package. Debian 12 ships 8.2.34-1~deb12u1 and keeps bookworm under Debian LTS until 30 June 2028, well past the upstream 8.2 date. Debian 11's PHP 7.4 package, on the other hand, left Debian LTS on 31 August 2026.
So ask who built this PHP. If it came from your distribution's repositories and that release is still covered, watch its security tracker for a backport (whether one appears for these flaws is the distribution's call). A third-party repository, hand-compiled build, frozen container image or vendor appliance: assume nobody will patch it.
Stopgaps for an end-of-life PHP server you cannot upgrade this week
None of these is a fix. Each one shrinks who can reach the vulnerable code or limits what an attacker gets if they do. Do them in this order; the first two carry most of the weight, and they match CERT-EU's advice for anyone who cannot patch: cut internet-facing applications off from public networks until a fix exists.
| Measure | What it helps with | What it does not do |
|---|---|---|
| Take it off the public internet (VPN, private network, IP allowlist) | Removes anonymous remote attackers from the picture | Protects nothing from users or machines already inside |
| Put authentication in front (reverse proxy basic auth or SSO) | Requests must authenticate before reaching PHP | Breaks public sites; a logged-in attacker still reaches PHP |
| WAF or reverse proxy rules | Rate limits, blocks known-bad patterns, quick to deploy once rules exist | No signature can exist before the technical details are public; expect a gap, and bypasses after |
| Isolate in a container or separate VM | Limits what a compromised PHP process can touch on the host | The app's own data and credentials are still exposed |
| Dedicated, unprivileged PHP-FPM user | A shell runs as a nobody account, not as the web server or deploy user | Anything that user can read (config files, database passwords) is still readable |
| Remove unused SAPIs (php-cgi, mod_php you do not use) | Closes paths like the 2024 php-cgi bug class | Does nothing if the flaw sits in the engine itself |
1. Shrink who can reach it
An internal tool, admin back office or partner-only API does not need to sit on the open internet; a VPN or an IP allowlist at the reverse proxy is often a one-hour change. A pattern for nginx (untested here; adapt the ranges):
# untested pattern: only the office range and the VPN subnet reach the legacy app
location / {
allow 203.0.113.0/24;
allow 10.8.0.0/24;
deny all;
# ... existing proxy_pass / fastcgi_pass lines
}Be honest about WAF rules: a managed WAF can only block this traffic once someone knows what it looks like, and the details arrive with the release. Turn on what your provider offers and keep the isolation anyway.
2. Run the PHP workers as a nobody account
The PHP-FPM configuration reference warns that "an exposed FastCGI endpoint allows arbitrary code execution", so the pool should listen on a Unix socket (or, over TCP, set listen.allowed_clients, which is unset by default and then accepts any address), and in containers the FPM port should not be published on the host. We validated this pool on the official php:8.1-fpm image (PHP 8.1.34) after creating the user with useradd -r -s /usr/sbin/nologin legacyapp; php-fpm -t returned "configuration file /usr/local/etc/php-fpm.conf test is successful".
[legacy]
user = legacyapp
group = legacyapp
listen = /run/php/legacy.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 5
security.limit_extensions = .php
clear_env = yes
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen
php_admin_value[open_basedir] = /var/www/legacy:/tmpThat test checks syntax, not your app: code that shells out to an image tool breaks once proc_open is disabled, so try it on staging. Our view on limits: disable_functions and open_basedir work inside PHP, and a memory-corruption bug in the engine could step around them. They limit damage; they are not a patch. Our hardening companion guide goes deeper on pool settings.
3. Drop the SAPIs you do not use
Old servers often carry both mod_php and php-cgi. On Apache, apache2ctl -M | grep -i php lists the loaded PHP module (on the official php:8.1-apache image it prints php_module (shared)). If the site runs through FPM, disable the module; if nothing needs php-cgi, remove the package.
PHP extended support: who sells patches for EOL branches
If the code cannot move this quarter, paying someone to backport security fixes is a legitimate bridge. These are the options we could verify on the vendors' own pages on 21 October 2026. Everything in the table is the vendor's description of its offering, not something we tested; none of the pages shows a price.
| Option | What the vendor page says | Fits when |
|---|---|---|
| Ubuntu Pro (ESM) | Security updates "for high and critical CVEs" for packages in main and universe; Ubuntu 22.04 standard support to May 2027, Pro to May 2032; 20.04 Pro to May 2030 | You run the PHP that Ubuntu itself packaged |
| Debian ELTS by Freexian | Up to 10 years of security support, but "only the packages used by the customers", limited architectures; Debian 11 to 30 Jun 2031, Debian 10 to 30 Jun 2029; quote on request | Debian 11 (its PHP 7.4 package) or an older Debian release with the distro PHP |
| Red Hat Application Streams | RHEL 9's PHP 8.1 stream retired May 2025; PHP 8.0 on RHEL 9 and PHP 7.4 on RHEL 8 are "full life" streams that follow the RHEL release; after a stream retires, customers upgrade or run "self-supported" | You are on RHEL's default PHP stream and a RHEL subscription |
| TuxCare extended lifecycle support for PHP (ELS) | "CVE patches for PHP 5.2 through 8.x versions past end of life", delivered through your package manager; lists Windows and major Linux distributions | Very old branches (5.x, 7.x) or mixed fleets |
| Zend PHP LTS | LTS for PHP 7.2, 7.3, 7.4 and 8.1 with patches for "critical and high-severity PHP CVEs" from a secure repository; says 8.1 LTS runs through December 2027 and 7.4 through December 2026 | You want a vendor-built PHP runtime with a support contract |
Before you sign anything, ask each vendor in writing: will you ship a fix for the 21 October 2026 flaws on my exact branch, and by when? None of the pages above answers that, and they cannot until the details are public. Also ask how the patched build reports its version, because your scanner will keep flagging "end-of-life version of PHP detected" on a patched 7.4 and you will need the vendor's advisory to close the finding.
A migration plan off end-of-life PHP
Extended support buys months; migration is the fix, and it goes faster when tools find the breakage first. This is the order we would use for a typical 7.4 or 8.1 app stuck past PHP end of life.
Step 1: inventory and pick the target
List every app on the EOL host with its framework or CMS version, Composer dependencies and loaded extensions. Then pick 8.4 (security fixes until 31 December 2028) or 8.5 (until 31 December 2029); choosing 8.2 today means doing this again in ten weeks.
Step 2: run the old code on the new PHP in Docker
The cheapest check there is: mount the code into the official image for the target version and run it with every notice on, without touching the server. If containers are new to you, our Docker course covers the basics.
docker run --rm -v "$PWD":/app -w /app php:8.4-cli \
php -d error_reporting=-1 -d display_errors=1 src/legacy.phpWe wrote a small legacy file with four typical problems (a dynamic property, ${} string interpolation, utf8_encode() and explode(',', null)) and ran it on three images on 21 October 2026:
- php:7.4-cli (7.4.33): runs clean, no notices.
- php:8.1-cli (8.1.34): one notice, "explode(): Passing null to parameter #2 ($string) of type string is deprecated".
- php:8.4-cli (8.4.26): four notices, including "Creation of dynamic property Order::$note is deprecated" and "Function utf8_encode() is deprecated since 8.2".
Two gotchas: deprecations become removals in a later major version, so fix them rather than silence them; and the 7.4 and 8.1 images are frozen (our 7.4 image was built in November 2022), fine for comparison, never for production.
Step 3: a PHP compatibility check with PHPCompatibility
PHPCompatibility is a ruleset for PHP_CodeSniffer that flags syntax and functions removed or deprecated across versions. Its README now says Composer is the only supported install and points at the 10.0 development line (the latest stable tag, 9.3.5, is from 2019):
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcompatibility/php-compatibility:"^10.0.0@dev"
vendor/bin/phpcs -p src --standard=PHPCompatibility --runtime-set testVersion 8.2-Tested in the composer:2 image (it resolved PHP_CodeSniffer 4.0.4), the scan reported "FOUND 0 ERRORS AND 2 WARNINGS": the ${name} interpolation and utf8_encode(). It did not flag the dynamic property or the null argument, because those depend on runtime types. WordPress code should use the separate PHPCompatibilityWP ruleset.
Step 4: let Rector rewrite, let PHPStan catch the rest
Rector rewrites code to newer syntax. A minimal rector.php:
<?php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withPhpSets(php84: true);Run vendor/bin/rector process --dry-run first. On Rector 2.7.0 the dry run proposed two changes: "Hello ${name}" to "Hello {$name}", and utf8_encode(...) to mb_convert_encoding(..., 'UTF-8', 'ISO-8859-1'). Rector's docs suggest withPhpLevel() for big legacy codebases, raising the level one step at a time so each diff stays reviewable.
PHPStan analyses types, and its phpVersion parameter lets it judge code as if it ran on a different PHP:
parameters:
level: 5
paths:
- src
phpVersion: 80400PHPStan 2.3.1 found exactly the two issues the other tools missed: "Access to an undefined property Order::$note" and "Parameter #2 $string of function explode expects string, null given."
| Issue in the sample | PHP 8.4 runtime | PHPCompatibility | Rector | PHPStan |
|---|---|---|---|---|
| Dynamic property | Deprecated notice | Missed | Not changed | Found |
${} interpolation | Deprecated notice | Found | Fixed | Not reported |
utf8_encode() | Deprecated notice | Found | Fixed | Not reported |
explode(',', null) | Deprecated notice | Missed | Not changed | Found |
No single tool caught all four; the three together did. Run all of them in CI so a regression fails the build (our Docker and CI/CD pipeline guide shows the pipeline side), and keep your test suite as the final judge.
Step 5: know the breaking changes by version
The php.net migration guides are the checklist. The changes that bite legacy apps most often:
- 5.6 to 7.0: the
mysql,eregandmssqlextensions were removed. Ancient WordPress and custom code show it as the missing MySQL extension error. - 7.4 to 8.0:
0 == "foo"is now false (it was true);each(),create_function()and__autoload()are gone; methods named after their class are no longer constructors;matchis a reserved word. - 8.0 to 8.1: passing null to non-nullable parameters of built-in functions is deprecated, which floods logs in older code.
- 8.1 to 8.2: dynamic properties are deprecated unless the class has
#[\AllowDynamicProperties];${}interpolation andutf8_encode()/utf8_decode()are deprecated. - 8.3 to 8.4: implicitly nullable parameters (
function foo(T $a = null)) are deprecated; write?T $a = null.
Jumping 7.4 straight to 8.4 means reading every guide in between: 8.0, 8.1, 8.2, 8.3 and 8.4.
Step 6: WordPress, plugins and frameworks
WordPress recommends PHP 8.3 or newer. According to the core team's compatibility table, WordPress 6.9, 7.0 and 7.1 support PHP 8.5, while 6.7 and 6.8 stop at 8.4, and WordPress 7.0 dropped PHP 7.2 and 7.3. So the order is: update WordPress core, then the plugins and theme, then switch PHP on staging. Plugins are where it usually breaks; abandoned ones with no update need replacing or patching by hand. For Laravel, Symfony or Drupal, check which framework release supports your target PHP (its composer.json declares the PHP range it accepts) and plan the framework upgrade together with the PHP one.
Cut over with a rollback ready: keep the old pool or image for a week and watch the error log for anything the tools missed.
Help moving an old PHP or WordPress app
If you have a legacy PHP or WordPress site that cannot move on its own, our studio, Pravinya Studio, can plan and carry out the move to a supported PHP version, update or fix incompatible plugins and themes, and keep the site running meanwhile. We are a small team that mainly builds WordPress plugins, Shopify themes, mobile apps and AI features, so this is migration and code work, not incident response.
FAQ
When did PHP 8.1 reach end of life?
On 31 December 2025. The last release was 8.1.34. Ubuntu 22.04 still maintains its own 8.1 package, and Zend says its paid 8.1 LTS runs through December 2027.
When is end of life for PHP 8.2?
Security support ends on 31 December 2026. Debian 12 keeps its 8.2 package under Debian LTS until 30 June 2028, but for new work pick 8.4 or 8.5.
What does "end-of-life version of PHP detected" mean in a scan?
The scanner read a version from a branch past its php.net end of life date. A distro-maintained package (Ubuntu's 8.1, a RHEL full-life stream) may carry backported fixes; a self-compiled or third-party build does not. Either way, plan the migration.
Will a WAF protect an EOL PHP server from php2shell?
Only partly. No rule can target the flaws before their details are public. Use it as one layer and take the server off the public internet until you upgrade.
Last updated / tested on: 21 October 2026, 11:36 UTC (php.net last checked 11:36 UTC; the security release was not yet published). Tested on Docker Engine 29.1.2 with official php:7.4-cli (7.4.33), php:8.1-cli (8.1.34), php:8.1-fpm, php:8.1-apache and php:8.4-cli (8.4.26) images; composer:2 (PHP 8.5.11) with PHPCompatibility dev-develop, PHP_CodeSniffer 4.0.4, Rector 2.7.0 and PHPStan 2.3.1. Network and nginx snippets untested.