On October 21, 2026, the PHP project shipped fixes for two critical bugs in PHP itself. Wiz Research, which found them with others, calls the pair PHP2Shell. Chained together, they let someone with no account run code on the server, and Wiz demonstrated it against the stock wordpress, drupal, and joomla container images. Cloudflare said it was already seeing attack traffic the same day.
If you self-host, the natural reaction is "I don't run WordPress." That's the trap. A good share of the containers in a typical self-hosted stack are PHP applications that never mention PHP: the speed-test dashboard, the budgeting app, the RSS reader, the webmail, the wiki. Each image carries its own copy of PHP, and each one has to be rebuilt by whoever publishes it before docker compose pull fixes anything.
This guide covers how to find the PHP in your stack, which popular self-hosted apps have it, why some images get patched within hours and others take days, and what to do until yours is fixed.
The short version: find out which of your containers run PHP and which version. Take anything internet-facing off the internet, or put real authentication in front of it, until a rebuilt image exists. Then pull, and check the version again, because a fresh image isn't always a patched one. A handful of popular images won't get the fix from a routine rebuild at all, and for those the answer is a different image or a nudge to the maintainer.
What PHP2Shell is
PHP2Shell is two vulnerabilities, CVE-2026-109618 and CVE-2026-109619, each rated 9.2 (critical) under CVSS 4.0. They are bugs in the PHP runtime, not in any application, so an app's own updates don't fix them and its login page doesn't stand in the way. Wiz's write-up says even a "Hello World" script on an affected version can be vulnerable.
Simplified: one bug lets a crafted upload run PHP out of memory while a request is only half set up, and the cleanup code then trusts memory that was never valid. The other lets internal state from one request outlive it, so a later request handled by the same PHP worker uses memory that has already been freed. Both happen while PHP is still processing the incoming request, before the application's code, and its login check, ever runs.
- Fixed in PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, all released October 21.
- No official release for 8.1 and older. The PHP security team published minimal source patches for 7.4, 8.0, and 8.1 for people who build PHP themselves, and some Linux distributions backport fixes to their own packages. A Docker image built on the official
php:8.1image, or anything older, gets neither. - Server setups. The advisories confirm remote code execution on Apache with mod_php for both bugs, and on PHP-FPM through the session-handling path of CVE-2026-109619. They call FrankenPHP "likely susceptible" to CVE-2026-109618. Most self-hosted PHP images run one of the first two.
- Exploitation. Cloudflare reported attack traffic against its customers, with attackers changing payloads to get past its rules. Neither CVE was in CISA's Known Exploited Vulnerabilities catalog as of October 21.
Step 1: find the PHP in your stack
Run this on each Docker host. It asks every running container whether it has a PHP binary and prints the version if it does:
for c in $(docker ps --format '{{.Names}}'); do
v=$(docker exec "$c" sh -c '
for b in php php85 php84 php83 php82 php81 php8 php7; do
command -v "$b" >/dev/null 2>&1 && exec "$b" -n -r "echo PHP_VERSION;"
done
command -v frankenphp >/dev/null 2>&1 && exec frankenphp version
' 2>/dev/null)
if [ -n "$v" ]; then echo "$c: $v"; fi
done
It works in bash and zsh. The list of binary names covers images that install PHP from Alpine packages, which name it php84 rather than php. Containers with no shell won't show up. That's rare for a PHP image, but check any you know run PHP by hand.
Anything below 8.5.12, 8.4.27, 8.3.36, or 8.2.35 is vulnerable. Anything on 8.1 or older won't be fixed by an upstream release at all. The script reports the command-line PHP, which in practically every image comes from the same build as the web worker.
Step 2: the apps that don't look like PHP
Nobody running WordPress, Drupal, Joomla, MediaWiki, phpMyAdmin, or Nextcloud needs to be told they run PHP. The list below is the rest: popular self-hosted apps whose names, docs, and Docker images rarely mention it. For each, the third column says where the recommended image gets its PHP, which decides who has to act before a pull fixes it (Step 3 explains the categories). Everything here was checked against each project's Dockerfiles and registry on October 21, 2026, between 15:00 and 16:20 UTC. Image details change quickly, so treat the notes as a snapshot and the Step 1 script as the source of truth for your own containers.
"LinuxServer.io" means the image is built by LinuxServer.io on Alpine Linux and gets PHP from Alpine's packages. "Official php image" means the Dockerfile starts FROM php:….
Dashboards and home-lab utilities
| App | What it is | Where its PHP comes from |
|---|---|---|
| Heimdall | App dashboard (Laravel) | LinuxServer.io |
| Organizr | Dashboard and single sign-on for media servers | Its own image: PHP 8.1.26 on Alpine 3.18, last pushed December 2023 |
| Speedtest Tracker | Scheduled internet speed tests (Laravel) | LinuxServer.io, which the project's docs recommend |
| LibreSpeed | Self-hosted speed test | Official php image by default; Alpine and LinuxServer.io variants |
| Wallos | Subscription tracker | Official php image; rebuilt when the app is updated |
| Grocy | Groceries and household management | LinuxServer.io, on Alpine 3.23 (PHP 8.5.6) |
Monitoring and network
| App | What it is | Where its PHP comes from |
|---|---|---|
| Zabbix | Monitoring. The server is C; the web frontend container is PHP | Alpine or Ubuntu packages, depending on the tag |
| LibreNMS | Network monitoring (Laravel) | Alpine packages; the current image is on Alpine 3.23 (PHP 8.4.21), and the repository moved to 3.24 on October 21 |
| phpIPAM | IP address management | Alpine packages; version tags like v1.8.3 aren't rebuilt |
| Cacti | Network graphing | No official image. The most-pulled community image runs PHP 7.2 on CentOS 8, last pushed May 2021 |
| App | What it is | Where its PHP comes from |
|---|---|---|
| mailcow | Mail server suite. Its admin UI and API run in the php-fpm-mailcow container |
Official php:8.2-fpm-alpine3.21, a tag that stopped updating in October 2025; image pinned in compose, updated with update.sh |
| Roundcube | Webmail | Official php image (Debian) for the apache and fpm variants; fpm-alpine uses a frozen tag (PHP 8.4.15) |
| SnappyMail | Webmail | Official php image; the last published image is from October 2024 (PHP 8.2.24) |
| Mailu | Mail server suite. Python, except the webmail container (Roundcube and SnappyMail) | Alpine 3.20 packages in the current release (PHP 8.3.15) |
Passwords and secrets
| App | What it is | Where its PHP comes from |
|---|---|---|
| Passbolt CE | Team password manager (CakePHP) | Debian 13 packages; already on 8.4.27 on October 21. The sample compose file pins a digest, so update it |
| PrivateBin | Encrypted pastebin | Alpine packages; rebuilt daily |
| 2FAuth | Two-factor code manager (Laravel) | Alpine packages; new images only with app releases |
Money and business
| App | What it is | Where its PHP comes from |
|---|---|---|
| Firefly III | Personal finance (Laravel), plus its Data Importer | Official php image, via serversideup/php and Firefly's own base image; the app image is rebuilt on release |
| Invoice Ninja | Invoicing (Laravel) | Official php image (invoiceninja/invoiceninja-debian). The older invoiceninja/invoiceninja image is deprecated and still runs PHP 8.2.28 |
| Kimai | Time tracking (Symfony) | Official php image with Apache for :stable; LinuxServer.io variant |
| Akaunting | Accounting (Laravel) | Official php:8.1-apache (PHP 8.1.34); no image pushed since March 2026 |
| Dolibarr | ERP and CRM | Official php image, rebuilt weekly; tags for Dolibarr 16 to 18 are on PHP 8.1 |
| FreeScout | Help desk | nfrastack's image on Alpine packages; it already turns off session upload progress |
| Snipe-IT | IT asset management (Laravel) | Ubuntu 24.04 packages; latest was already patched on October 21 |
| GLPI | IT service management (Symfony) | Official php image with Apache |
| Part-DB | Electronic parts inventory (Symfony) | Debian plus deb.sury.org packages; a FrankenPHP variant also exists |
Reading, bookmarks, and wikis
| App | What it is | Where its PHP comes from |
|---|---|---|
| FreshRSS | RSS reader | Debian 13 packages; the 1.30.3 image was already patched on October 21. Alpine and LinuxServer.io variants wait on Alpine |
| Tiny Tiny RSS | RSS reader | Alpine packages, from the community continuation at tt-rss/tt-rss. The original project retired on November 1, 2025, and its old cthulhoo/ttrss-* images are gone |
| Wallabag | Read-it-later (Symfony) | Alpine 3.19 with PHP 8.1.32; last pushed October 2025 |
| Shaarli | Bookmarks | Alpine packages |
| LinkAce | Bookmarks (Laravel) | Official php image, via its own base image |
| RSS-Bridge | Generates RSS feeds for sites without them | Debian 12 packages, which Debian had already fixed; needs a rebuild |
| BookStack | Wiki and documentation (Laravel) | No official image; the docs list LinuxServer.io and solidnerd/bookstack (official php image) |
| DokuWiki | Flat-file wiki | Official php image, checked daily and rebuilt when it changes; LinuxServer.io variant |
Photos, music, and media
| App | What it is | Where its PHP comes from |
|---|---|---|
| AzuraCast | Web radio station | Official php image; the stable channel is rebuilt only on release (PHP 8.5.9) |
| Koel | Music streaming (Laravel) | Official php:8.4.8-apache-bookworm, pinned to that exact PHP version |
| Ampache | Music streaming | Debian plus deb.sury.org packages, which are already fixed; needs a rebuild |
| Lychee | Photo gallery (Laravel) | FrankenPHP, pinned by digest; LinuxServer.io variant |
| Piwigo | Photo gallery | Its own image on Alpine packages; the LinuxServer.io image is on Alpine 3.22 (PHP 8.4.16) |
| Castopod | Podcast hosting | FrankenPHP via serversideup/php; last image February 2026 (PHP 8.4.18) |
Fediverse
| App | What it is | Where its PHP comes from |
|---|---|---|
| Pixelfed | Photo sharing (Laravel) | FrankenPHP via serversideup/php; the repository's compose file builds the image locally |
| Friendica | Social network | Docker Official Image, on the official php image with Apache |
| Mbin | Reddit-style threadiverse (Symfony) | FrankenPHP; rebuilt on release |
Everything else
| App | What it is | Where its PHP comes from |
|---|---|---|
| Nextcloud All-in-One | Nextcloud installer and manager. Its admin interface is a PHP app | Official php image, pinned to exact versions, bumped to the fixed ones on October 21 |
| Kanboard | Kanban board | Alpine packages |
| Leantime | Project management (Laravel) | Official php image |
| Baïkal | CalDAV and CardDAV server | Community ckulka/baikal:latest on official php:8.1-apache (PHP 8.1.34) |
| Monica | Personal CRM (Laravel) | Docker Official Image on php:8.2-apache; the app's last release was May 2024 |
| YOURLS | URL shortener | Docker Official Image, on the official php image with Apache |
| Shlink | URL shortener, on the RoadRunner app server | Official php:8.5-alpine3.22, frozen at 8.5.7 since June |
| Matomo | Web analytics | Docker Official Image, on the official php image with Apache |
| Pterodactyl, Pelican | Game server panels (Laravel). The Wings daemon on each node is Go | Official php image |
| Adminer | Database admin, often added to a compose file and forgotten | Docker Official Image, running PHP's built-in web server |
A few of these deserve a second look.
- phpMyAdmin's image name. The Docker Official Image is
phpmyadmin. The olderphpmyadmin/phpmyadminimage, which many compose files still reference, was last pushed in October 2025 with PHP 8.3.26; its README says it is "maintained as a courtesy." - The Nextcloud All-in-One interface runs on ports 8080 and 8443 and has the Docker socket mounted, so code execution there amounts to control of the host. Keep those ports off the internet.
- Images running Apache with mod_php, the setup where the advisories confirmed full exploitation of both bugs, include every official
-apachevariant (Nextcloud, WordPress, Matomo, Friendica, YOURLS, Monica), plus Akaunting, Dolibarr, Kimai's:stable, GLPI, DokuWiki, FreshRSS, and Snipe-IT. - Shlink and Adminer don't run PHP the usual way (RoadRunner and PHP's built-in server). The advisories don't address either setup, so treat both as affected.
Not in a container, but PHP too. The web interfaces of Unraid, pfSense, and OPNsense are PHP applications. They get PHP from their vendors' own updates, so watch for those, and keep the admin pages off the internet in any case.
No longer PHP. Pi-hole v6 dropped PHP: its web interface is now served by Pi-hole's own C daemon. Pi-hole v5 still ran lighttpd and PHP. ownCloud Infinite Scale and Pydio Cells are written in Go; classic ownCloud Server is still PHP.
Step 3: why pulling may not fix it yet
A PHP fix reaches your container in several hops: the PHP release, then whatever the image is built on, then the app's own image, then your pull. Each hop has a different maintainer, and the image is only as current as the slowest of them. Every self-hosted image gets its PHP in one of three ways.
Built on Docker's official php image. Most project-built images start from a line like FROM php:8.4-apache. So do Docker's own official images for apps, such as nextcloud, wordpress, matomo, friendica, yourls, monica, phpmyadmin, and adminer. Docker's maintainers merged the PHP update into the official image definitions at 15:16 UTC on October 21. At 16:00 UTC, the patched tags were listed on Docker Hub but couldn't be pulled yet, and floating tags such as php:8.4-apache still served 8.4.26. Docker rebuilds its official app images when their base image changes, so those should follow on their own. Images built by the projects themselves follow whenever the project's build next runs. DokuWiki's checks the base image daily, and Roundcube's and Pelican's rebuild weekly. Many rebuild only when the app has a release, which can be weeks or months away. Timing matters too: LinkAce rebuilt its base image at 15:59 UTC on October 21 and still got PHP 8.5.11, because Docker's patched tags weren't live yet.
PHP from Alpine or Debian packages. Every LinuxServer.io PHP image works this way, as do the images for FreshRSS, Snipe-IT, Passbolt, Kanboard, PrivateBin, phpIPAM, 2FAuth, LibreNMS, Shaarli, Tiny Tiny RSS, and Zabbix's web frontend. These wait on the distribution first. Debian and Ubuntu moved fast: by the afternoon of October 21, Debian 12 and 13 and Ubuntu 22.04 and 24.04 all had fixed PHP packages, which is why the Passbolt, FreshRSS, and Snipe-IT images were already patched. Ondřej Surý's widely used PHP repository for Debian and Ubuntu, deb.sury.org, had all four fixed versions out by 13:36 UTC, and also backported the fix to its PHP 8.1 package. Alpine had not shipped yet: at 16:00 UTC, its 3.24 and edge repositories still had 8.5.11, 8.4.26, and 8.3.35. Once Alpine ships, images still need a rebuild. PrivateBin's images are rebuilt daily, and LinuxServer.io's when package versions change.
FrankenPHP. Lychee, Mbin, Castopod, and Pixelfed run on FrankenPHP, an app server with PHP compiled in, whose images are themselves built on the official php image. That adds a hop: Castopod and Pixelfed go official php image, then FrankenPHP, then the serversideup/php base, then the app.
Five ways a rebuild doesn't help
Some images will stay vulnerable even after the PHP release, the base image update, and a fresh build. As of October 21:
- The image is built on an old Alpine release. Alpine's older branches had stopped getting new PHP point releases: as of October 21, Alpine 3.23 still had PHP 8.5.6 from May, 3.22 had 8.4.16 from December 2025, and 3.20 had 8.3.15 from December 2024. Rebuilding on those branches ships the same old PHP. That catches LinuxServer.io's Grocy (Alpine 3.23, PHP 8.5.6), LinuxServer.io's Piwigo (3.22, PHP 8.4.16), and the webmail container in Mailu's current release (3.20, PHP 8.3.15).
- The image uses an official
phptag that's no longer updated. Official tags tied to an old Alpine release stop moving when Docker drops that release. mailcow's PHP container is built onphp:8.2-fpm-alpine3.21, last updated in October 2025; a pending change moves it to a current base. Roundcube'sfpm-alpinevariant usesphp:8.4-fpm-alpine3.21(PHP 8.4.15). Shlink usesphp:8.5-alpine3.22, frozen at 8.5.7 since June. The deprecatedinvoiceninja/invoiceninjaimage, rebuilt as recently as October 18, usesphp:8.2-fpm-alpine3.20and ships PHP 8.2.28. - The Dockerfile pins an exact PHP version. Koel's image is built on
php:8.4.8-apache-bookworm, so it stays on PHP 8.4.8 until someone edits the line. Nextcloud All-in-One pins exact versions too, but it bumped them to 8.5.12 and 8.4.27 on October 21 and is waiting on Docker's images. - Your compose file pins the image. mailcow pins its image tags in its compose file and updates through its own
update.sh. Passbolt's sample compose file pins a digest. phpIPAM's version tags, likev1.8.3, aren't rebuilt. Pixelfed's compose file builds the image locally, sopulldoes nothing and the fix needsdocker compose build --pull. - It's on PHP 8.1 or older. No official PHP release will fix it. Ubuntu 22.04 and deb.sury.org backported the fix to their PHP 8.1 packages, but the official
php:8.1images were last built at the turn of the year and won't be rebuilt. Akaunting's image runs the officialphp:8.1-apacheand hasn't been pushed since March 2026. Wallabag's runs PHP 8.1.32 on Alpine 3.19, last pushed in October 2025. Organizr's runs PHP 8.1.26 on Alpine 3.18, last pushed in December 2023.ckulka/baikal:latest, the community image Baïkal's own docs point to, and Dolibarr's tags for versions 16 to 18 are built on official 8.1 images, so their weekly rebuilds re-ship the same PHP. The most-pulled community Cacti image runs PHP 7.2 on CentOS 8 and hasn't been pushed since May 2021.
The Baïkal and Piwigo cases are the warning: those images were rebuilt within the past week and still carry old PHP. A recent push date says nothing about the PHP inside. Run the script from Step 1 after every pull and look at the version.
Until your image is patched
The PHP advisories say plainly that updating is the only complete fix. Until a rebuilt image exists, the goal is to keep anonymous requests away from PHP, and to change a few settings that narrow the attack.
Take it off the internet. An app reachable only on your LAN, or only over a VPN such as Tailscale or WireGuard, can only be attacked by someone already inside. For most homelab apps this is the cheapest stopgap and the most complete one.
Put authentication in front of PHP, not inside it. The app's own login page doesn't help, because PHP processes the request before the app's code runs. Forward authentication at the reverse proxy does help: Authelia, Authentik, oauth2-proxy, or Cloudflare Access turn anonymous requests away before they reach the container. A plain reverse proxy with no authentication does nothing here; it forwards the malicious upload as-is. The catch is the bypass rules. Share links, CalDAV and CardDAV, mobile-app APIs, and webhooks often skip forward auth because their clients can't log in through it, and those paths stay exposed.
Cloudflare's WAF, if you already use it. Since 03:02 UTC on October 21, Cloudflare's managed rules block known PHP2Shell attempts on every plan with the WAF deployed, including Free. That covers hostnames proxied through Cloudflare, and Cloudflare Tunnel hostnames inherit the zone's WAF rules. Cloudflare calls it a stopgap, and it says attackers were already mutating payloads to get around it.
PHP settings that narrow it. The advisories list several, all to be set at the administrator level (a php.ini or conf.d file, or php_admin_value in an FPM pool) and followed by a restart of every PHP worker:
session.upload_progress.enabled=0removes a trigger that CVE-2026-109619 uses. The advisory suggests leaving it off even after patching if the app doesn't need upload progress.fatal_error_backtraces=0, on PHP 8.5 only, removes the confirmed trigger for the same bug.memory_limit=-1is the advisory's main configuration mitigation for CVE-2026-109618, and it is not complete. It also removes PHP's own memory cap, so a runaway request can use whatever RAM the container allows. Check that the app doesn't set its own limit back through an environment variable or per-directory config.pm.max_requests=1in a PHP-FPM pool makes each worker exit after one request, so no state carries over to the next. It costs performance, which on a single-user homelab app may not matter.
To add an ini file, mount it into the directory your image reads. Images built on the official php image read /usr/local/etc/php/conf.d/. LinuxServer.io's PHP images load any .ini file in /config/php/, which is already on your volume.
What the patch says about your images
A security release like this one is the cleanest test of how an image is maintained, because it asks every publisher the same question on the same day. By the afternoon of October 21 (UTC), the images that were already patched had something in common: they build on Debian or Ubuntu packages and rebuild when those packages change. The images at risk of staying vulnerable share the opposite traits: a base pinned to an exact PHP version or to an old distribution release, or a build that only runs when someone cuts an app release.
That's worth knowing the next time you pick an image. Many of these apps have two or three to choose from (the project's own, LinuxServer.io's, a community build), and they don't age the same way. Look at the FROM line and at what triggers a rebuild, not only at the app version on the tag.