PHP End of Life: Survival Guide for 8.1, 7.4 and 5.x Servers

• Tapovan

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.

BranchStatus todaySecurity fixes until / EOL dateLast or current release
8.5Active support (to 31 Dec 2027)31 Dec 20298.5.11
8.4Active support (to 31 Dec 2026)31 Dec 20288.4.26
8.3Security fixes only31 Dec 20278.3.35
8.2Security fixes only31 Dec 20268.2.34
8.1End of life31 Dec 20258.1.34
8.0End of life26 Nov 20238.0.30
7.4End of life28 Nov 20227.4.33
7.3 / 7.2End of life6 Dec 2021 / 30 Nov 20207.3.33 / 7.2.34
7.1 / 7.0End of life1 Dec 2019 / 10 Jan 20197.1.33 / 7.0.33
5.6End of life31 Dec 20185.6.40
5.5 / 5.4 / 5.3End of life21 Jul 2016 / 3 Sep 2015 / 14 Aug 20145.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.

MeasureWhat it helps withWhat it does not do
Take it off the public internet (VPN, private network, IP allowlist)Removes anonymous remote attackers from the pictureProtects nothing from users or machines already inside
Put authentication in front (reverse proxy basic auth or SSO)Requests must authenticate before reaching PHPBreaks public sites; a logged-in attacker still reaches PHP
WAF or reverse proxy rulesRate limits, blocks known-bad patterns, quick to deploy once rules existNo signature can exist before the technical details are public; expect a gap, and bypasses after
Isolate in a container or separate VMLimits what a compromised PHP process can touch on the hostThe app's own data and credentials are still exposed
Dedicated, unprivileged PHP-FPM userA shell runs as a nobody account, not as the web server or deploy userAnything 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 classDoes 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:/tmp

That 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.

OptionWhat the vendor page saysFits 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 2030You run the PHP that Ubuntu itself packaged
Debian ELTS by FreexianUp 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 requestDebian 11 (its PHP 7.4 package) or an older Debian release with the distro PHP
Red Hat Application StreamsRHEL 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 distributionsVery old branches (5.x, 7.x) or mixed fleets
Zend PHP LTSLTS 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 2026You 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.php

We 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: 80400

PHPStan 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 samplePHP 8.4 runtimePHPCompatibilityRectorPHPStan
Dynamic propertyDeprecated noticeMissedNot changedFound
${} interpolationDeprecated noticeFoundFixedNot reported
utf8_encode()Deprecated noticeFoundFixedNot reported
explode(',', null)Deprecated noticeMissedNot changedFound

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, ereg and mssql extensions 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; match is 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 and utf8_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.

Last updated: October 21, 2026
an "open and free" initiative. Powered by Blogger.