Ubuntu’s fix for PHP2Shell (CVE-2026-109618 and CVE-2026-109619) keeps the PHP version number you already had, so php -v on a patched Ubuntu 24.04 server still says 8.3.6. That trips people up. The fix lives in the package release, 8.3.6-0ubuntu0.24.04.13, and the only proof is the package changelog. If you check the version string the way the upstream advisories tell you to, you will think you are still exposed. The ondrej PPA adds a second trap: its PHP 8.2 and newer builds do change version, but its 8.1, 8.0 and 7.4 builds keep the old number, so the same version string can be either vulnerable or patched.
This guide patches PHP2Shell on Ubuntu 26.04, 24.04 and 22.04, proves the fix with the changelog and confirms PHP-FPM and Apache run the new build. It also covers the ondrej PPA, where the version changes for 8.2 and newer, and what Ubuntu 20.04 and 18.04 need. Every command below, apart from the Ubuntu Pro step, was run on Ubuntu 26.04, 24.04 and 22.04 lab VMs and a fresh 24.04 container on 21 and 22 October 2026, starting from the vulnerable package each release shipped.
Check the Fixed PHP Versions on Ubuntu
Canonical published the fixes in USN-8948-1 for the supported releases and USN-8948-2 for the Ubuntu Pro ones, on 21 October 2026. Any package release lower than the one in the “Fixed” column is vulnerable, whichever PHP features your sites use:
| Ubuntu release | PHP package | Vulnerable build we started from | Fixed package release | Where it comes from |
|---|---|---|---|---|
| 26.04 LTS | php8.5 | 8.5.4-0ubuntu1 | 8.5.4-0ubuntu1.5 | resolute-security, free |
| 24.04 LTS | php8.3 | 8.3.6-0maysync1 | 8.3.6-0ubuntu0.24.04.13 | noble-security, free |
| 22.04 LTS | php8.1 | 8.1.2-1ubuntu2 | 8.1.2-1ubuntu2.28 | jammy-security, free |
| 20.04 LTS | php7.4 | not tested | 7.4.3-4ubuntu2.29+esm5 | Ubuntu Pro (ESM Infra) |
| 18.04 LTS | php7.2 | not tested | 7.2.24-0ubuntu0.18.04.17+esm14 | Ubuntu Pro (ESM Infra) |
The first three rows are what the lab VMs reported before and after the update. The two Pro rows come straight from Canonical’s CVE tracker, because a Pro subscription was not part of this test.
Find Every PHP Build on the Server
Patch the PHP that actually serves requests, not only the one your shell runs. A server that has been around for a few years often carries two or three php8.x branches side by side, plus an Apache module nobody remembers enabling. List the installed PHP packages first:
dpkg -l 'php*' 'libapache2-mod-php*' | awk '/^ii/{print $2, $3}'
In a fresh 24.04 container pinned to the stock build, it listed the shared php-common package and the five vulnerable php8.3 packages:
php-common 2:93ubuntu2
php8.3-cli 8.3.6-0maysync1
php8.3-common 8.3.6-0maysync1
php8.3-fpm 8.3.6-0maysync1
php8.3-opcache 8.3.6-0maysync1
php8.3-readline 8.3.6-0maysync1
Without mod_php installed, dpkg also prints “no packages found matching libapache2-mod-php*” on stderr, which is harmless. Next, check which PHP-FPM services are running and whether Apache loads mod_php. PHP’s own advisory rates mod_php the highest risk, because one anonymous request can reach the vulnerable code before your application runs:
systemctl list-units --type=service --state=running 'php*-fpm*'
sudo apache2ctl -M 2>/dev/null | grep -i php
Any php_module line means Apache runs mod_php, so it goes first on your list. No output from the second command means PHP-FPM is the only thing you need to restart later.
Patch PHP2Shell on Ubuntu
The fix is a normal security update, so apt sees it as soon as the package lists refresh. Upgrade only the PHP packages, so an emergency change does not drag a kernel or libc update (and a reboot) along with it. Start by refreshing the lists and checking what’s waiting:
sudo apt-get update
apt list --upgradable 2>/dev/null | grep php
On 24.04 every PHP package showed the security pocket as its source, which is what you want to see before going further:
php8.3-cli/noble-updates,noble-security 8.3.6-0ubuntu0.24.04.13 amd64 [upgradable from: 8.3.6-0maysync1]
php8.3-common/noble-updates,noble-security 8.3.6-0ubuntu0.24.04.13 amd64 [upgradable from: 8.3.6-0maysync1]
php8.3-fpm/noble-updates,noble-security 8.3.6-0ubuntu0.24.04.13 amd64 [upgradable from: 8.3.6-0maysync1]
php8.3-opcache/noble-updates,noble-security 8.3.6-0ubuntu0.24.04.13 amd64 [upgradable from: 8.3.6-0maysync1]
php8.3-readline/noble-updates,noble-security 8.3.6-0ubuntu0.24.04.13 amd64 [upgradable from: 8.3.6-0maysync1]
If you have ever added ppa:ondrej/php, run apt-cache policy php8.3-fpm (with your branch) before the next step. The PPA’s candidate is a higher version than Ubuntu’s, so the upgrade line below moves the package to the PPA build right away, not at some later routine upgrade. That still patches PHP2Shell, but it changes who supports the package, so make that choice deliberately.
Now upgrade every installed PHP 8 package and the Apache module in one go. The two globs match whatever branches you have, and --only-upgrade means apt never installs a PHP branch you do not already have, so the same line works on 26.04, 24.04 and 22.04:
sudo apt-get install --only-upgrade 'php8.*' 'libapache2-mod-php8.*'
apt prints a long run of “Skipping … it is not installed” lines for the branches you do not have. Ignore them. The part that matters is the upgrade count at the end. In the 24.04 container it moved all five packages to the fixed release:
5 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
Setting up php8.3-common (8.3.6-0ubuntu0.24.04.13) ...
Setting up php8.3-readline (8.3.6-0ubuntu0.24.04.13) ...
Setting up php8.3-opcache (8.3.6-0ubuntu0.24.04.13) ...
Setting up php8.3-cli (8.3.6-0ubuntu0.24.04.13) ...
Setting up php8.3-fpm (8.3.6-0ubuntu0.24.04.13) ...
On the 22.04 VM the same command took 12 seconds and upgraded the php8.1 packages to 8.1.2-1ubuntu2.28. The “1 not upgraded” is an unrelated package the container image held back. If you still run PHP 7.4 from the PPA, add 'php7.*' to the line, because the PHP 8 glob will not touch it.
Verify the PHP2Shell Fix Is Installed
Do not trust php -v here. Ubuntu backports the patch into the version it already ships, so the version string barely moves. On 24.04 it still reports 8.3.6 after the update, and only the build date gives it away. 22.04 is the exception, because its php -v prints the full package release (8.1.2-1ubuntu2.28):
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
The package changelog is the real proof. Search the CLI package’s copy for either CVE, swapping 8.3 for your branch. Every PHP install pulls in the CLI package, so this path exists on mod_php servers too, where the FPM one does not:
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.3-cli/changelog.Debian.gz
A patched 24.04 or 22.04 box names Ubuntu’s patch file and both CVE numbers:
- debian/patches/CVE-2026-109618_109619.patch: Avoid state corruption via
- CVE-2026-109618
- CVE-2026-109619
Ubuntu 26.04 returns eight lines instead of three. Five prerequisite patches come first (CVE-2026-109618_109619-pre1.patch to -pre5.patch, separate upstream bug fixes in ext/filter, ext/standard/string.c, iconv and the JIT that shipped in the same security update), then the main CVE-2026-109618_109619.patch line and the two CVE lines. Empty output on any release means the vulnerable build is still installed.
Use zgrep on the local file rather than apt-get changelog. The local file describes what is installed, while apt-get changelog fetches the changelog of the candidate version. On our 24.04 VM with the ondrej PPA enabled it failed outright with Changelog unavailable for php8.3=8.3.36-1+ubuntu24.04.1+deb.sury.org+1, because the PPA does not publish changelogs where apt looks for them.
Check memory_hard_limit_percent on 26.04 and 24.04
The upstream fix adds a new ini setting, memory_hard_limit_percent, and Ubuntu’s 8.5 and 8.3 backports carry it too. Asking PHP for it returns an empty string on a patched build and false on a vulnerable one, because the setting does not exist there:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
Our patched 26.04 and 24.04 VMs both answered with an empty string:
string(0) ""
Do not use this check on 22.04. Ubuntu’s php8.1 fix is the smaller backport PHP published for end-of-life branches, and it does not add the setting, so a fully patched 22.04 server still prints bool(false). Trust the changelog there.
Restart PHP-FPM and Apache
A patched file on disk protects nothing until every PHP process has restarted. Ubuntu’s packages handle that part: on all three VMs the php8.x-fpm service restarted during the upgrade, which you can see from its start time:
systemctl show -p ActiveEnterTimestamp php8.3-fpm
On the 24.04 VM it returned the minute of the upgrade:
ActiveEnterTimestamp=Wed 2026-10-21 21:57:22 UTC
On a mod_php server, add apache2 to the same systemctl show command to see Apache’s start time as well.
What has to restart depends on how PHP runs, and the package scripts cover the two common cases:
- PHP-FPM behind nginx or Apache:
php8.x-fpmrestarts itself during the upgrade - Apache with mod_php: the
libapache2-mod-php8.xupgrade restarts Apache - Queue workers, Supervisor jobs, Laravel Octane: nothing restarts them, so you do
Apache with mod_php behaved the same way. We put libapache2-mod-php8.1 at the vulnerable release on the 22.04 VM, ran the upgrade line above, and Apache’s master process came back under a new PID in the same minute, which means a full restart rather than a reload. Afterwards no Apache process still mapped the old libphp8.1.so. If your timestamp does not move (a customised unit, a held service, or a container image whose policy-rc.d blocks service restarts), restart by hand. Use a full restart for Apache rather than a reload, so the module is loaded fresh:
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2
Run both only if the timestamp check showed a service that did not restart, because an unnecessary Apache restart drops in-flight requests for a few seconds.
Check That Nothing Runs the Old Build
When apt replaces a library, a process that loaded the old copy keeps it mapped, and Linux marks that mapping (deleted). Checking for those mappings is the most reliable test that the restart actually happened. Limit it to PHP processes:
for p in $(pgrep -f 'php|apache2'); do sudo grep -qE ' /(usr|lib).*\(deleted\)$' /proc/$p/maps && echo "restart needed: $p $(tr '\0' ' ' < /proc/$p/cmdline)"; done
No output means every PHP process, including queue workers and other long-running CLI scripts, runs the patched build, and that is what the 24.04 VM printed after its upgrade. We filter on purpose. Run the same grep across every process after an upgrade and it also flags dbus-daemon, systemd-logind and agetty, which had nothing to do with PHP on our 24.04 VM. On Ubuntu, sudo needrestart -b -r l gives the same answer as a list of services.
For a one-shot audit of every PHP binary on the box, including PPA and control-panel builds, we wrote a read-only checker that runs the changelog and version checks for you. It’s covered in the PHP2Shell check and patch guide. Installed as /usr/local/bin/php2shell-check and run with sudo /usr/local/bin/php2shell-check on the patched 24.04 VM, which by then also carried the PPA’s PHP 8.4 and the FPM upload-progress drop-in from the end of this guide, it reported:
BINARY VERSION STATUS
/usr/bin/php8.3 8.3.6 FIXED (vendor backport in php8.3-cli), ini upload_progress=on
/usr/bin/php8.4 8.4.27 FIXED (upstream 8.4.27 or later), ini upload_progress=on
/usr/sbin/php-fpm8.3 8.3.6 FIXED (vendor backport in php8.3-fpm), ini upload_progress=off
/usr/sbin/php-fpm8.4 8.4.27 FIXED (upstream 8.4.27 or later), ini upload_progress=on
Running PHP processes still mapping replaced files (restart these):
none
It exited with status 0, which means every PHP build on the host is fixed and nothing still runs the old code, so the server is done.
Patch PHP From the ondrej PPA
Servers that run a newer PHP than the release ships, like PHP 8.4 on 24.04, usually get it from ppa:ondrej/php, which builds for 22.04 and 24.04 but not for 26.04. For PHP 8.2 and newer, the PPA works the opposite way to Ubuntu: it ships the upstream release, so the version number changes and 8.5.12, 8.4.27, 8.3.36 and 8.2.35 are the patched ones. The same apt-get install --only-upgrade line patches it. After adding the PPA on the 24.04 VM, php8.4 came straight from it at the fixed release:
ii php8.4-fpm 8.4.27-1+ubuntu24.04.1+deb.sury.org+1 amd64 server-side, HTML-embedded scripting language (FPM-CGI binary)
The PPA’s end-of-life branches are the exception to the version rule. PHP 8.1, 8.0 and 7.4 got PHP’s backported fix under the same upstream version, so only the package revision moves: 8.1.34-9, 1:8.0.30-25 and 1:7.4.33-31 (each followed by +ubuntu24.04.1+deb.sury.org+1 on 24.04) are the fixed ones. Installed on the 24.04 VM, the PPA’s php8.1 still printed PHP 8.1.34, and only the changelog showed the fix:
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.1-cli/changelog.Debian.gz
A patched 8.1.34-9 build returns the backport line:
+ [CVE-2026-109618, CVE-2026-109619]: Avoid state corruption via
Two more details catch PPA users out, and both showed up on the lab VM:
- The newest changelog entry is a one-line “No-change backport to noble”, and the CVE line sits in the entry below it
- Adding the PPA changes the upgrade path for PHP you got from Ubuntu
The first one only matters if you read the changelog with head, because zgrep still finds the CVE line. The second one is easy to miss: the 24.04 VM’s Ubuntu-built php8.3 suddenly had a PPA candidate:
php8.3-fpm:
Installed: 8.3.6-0ubuntu0.24.04.13
Candidate: 8.3.36-1+ubuntu24.04.1+deb.sury.org+1
Both builds are patched, so this is not a PHP2Shell problem, but the upgrade line from earlier, or any routine apt upgrade, moves that server from 8.3.6 to 8.3.36 and from Ubuntu’s support to Ondřej’s. Decide which one you want before that happens, rather than finding out from a changed php -v.
Patch Ubuntu 20.04 and 18.04 With Ubuntu Pro
Standard support for 20.04 and 18.04 has ended, so the free archive has no fix for their php7.4 and php7.2. Canonical patched both through Ubuntu Pro with ESM Infra enabled, and USN-8948-2 also covers 16.04 and 14.04 through the Legacy Support add-on. Pro is free for personal use on up to five machines, which covers a lot of small servers. On an attached machine, pro fix applies exactly the packages needed for a CVE:
sudo pro fix CVE-2026-109618
sudo pro fix CVE-2026-109619
We did not have a Pro-attached 20.04 machine in the lab, so treat the fixed releases in the table above as Canonical’s figures rather than ours. Without Pro, those servers have no package fix at all. Keep them behind the PHP2Shell mitigations and plan the release upgrade, because a 24.04 or 26.04 base gets this and the next PHP fix for free.
Keep Upload Progress Off After Patching
PHP’s advisory for CVE-2026-109619 calls turning session.upload_progress.enabled off prudent if you do not need the feature, because it is hardly used and hardly tested, and that holds after the patch too. On Ubuntu that setting has to go into the FPM tree, not the CLI one. The CLI reads /etc/php/8.3/cli/ while PHP-FPM reads /etc/php/8.3/fpm/, so a change in the wrong tree looks applied when you test from the shell and does nothing for your sites. Write it as a drop-in that package upgrades will not overwrite:
echo "session.upload_progress.enabled = Off" | sudo tee /etc/php/8.3/fpm/conf.d/99-php2shell.ini
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm
Then check the value PHP-FPM itself sees, which is the one that protects your sites:
sudo php-fpm8.3 -i | grep session.upload_progress.enabled
It should read Off, and on our VM it did, while php -r from the shell still said 1 because the CLI never read that file:
session.upload_progress.enabled => Off => Off
A 1 MB test upload through nginx still went through after the change, so normal file uploads keep working. For mod_php, the same line goes in /etc/php/8.1/apache2/conf.d/99-php2shell.ini (your branch in the path), followed by sudo systemctl restart apache2. On the 22.04 VM, a page served by mod_php went from upload_progress='1' to an empty value after that restart, while the CLI still reported 1. If you also run PHP on Debian or the RHEL family, the Debian patch guide and the Rocky Linux and AlmaLinux guide cover the differences. Red Hat shipped its AppStream errata on 21 October, but the branch you run decides whether that covers you, which the RHEL-family guide sorts out. For a fresh server, the PHP 8.5 install on Ubuntu 26.04 pulls the patched build from day one.