Safeguard
Supply Chain Security•

PHP2Shell: mod_php vs PHP-FPM, php-cgi, FrankenPHP and LiteSpeed

PHP rates Apache mod_php highest risk for PHP2Shell. See how PHP-FPM, php-cgi, FrankenPHP and LiteSpeed compare, and how to restart each after patching.

Safeguard Security Team
23 min read

PHP's own advisory rates Apache mod_php as the highest risk setup for PHP2Shell (CVE-2026-109618 and CVE-2026-109619), because a demonstrated attack fires before any application code runs. PHP-FPM, php-cgi, FrankenPHP and LiteSpeed are all affected too. Every one of them needs the patched PHP and a real restart.

Some teams read "mod_php is worst" as "FPM is fine". PHP did not say that. This post explains, at the process model level only, why the server API (SAPI) changes exposure, how to tell which SAPI you run, and how to restart each so the patched code is in memory. No exploit detail.

For background on the flaws, start with our PHP2Shell explainer. For fleet-wide discovery, see find and patch every PHP runtime after PHP2Shell.

What happened

Date (2026)EventSource
20 OctPHP posts an advance notice of two critical fixes, both CVSS v4.0 9.2php.net
20 to 21 OctDetails leak before the coordinated dateHelp Net Security, The Hacker News
21 OctPHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 ship. The advisory lists PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed as affected and calls the risk "highest for mod_php"php.net
21 OctTwo php-src GitHub advisories give per-SAPI analysis for each CVEGHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp
21 OctRed Hat rates CVE-2026-109618 Important (preliminary CVSS 3.1 of 8.1) and notes standard RHEL deployments use PHP-FPMRed Hat

Exploitation status is disputed. watchTowr's FAQ says its honeypots observed exploitation as of 21 October; Huntress said it had not seen any when it wrote. Neither claim is specific to one SAPI.

Why the SAPI matters at all

A SAPI connects the PHP engine to whatever receives HTTP requests. The engine is the same everywhere. What changes is where PHP lives, how long a process stays alive, how many requests it serves, and which account it runs as.

That matters because of how PHP describes the two flaws:

  • CVE-2026-109618: out-of-memory handling can interrupt PHP while request data is only partly set up, and cleanup then treats it as valid. Whether cleanup runs after a failed start is a SAPI decision.
  • CVE-2026-109619: state from one request can survive into the next request on the same worker. The php-src advisory says exploitation needs two related steps handled by the same persistent worker. How workers are reused is a SAPI decision.

So the SAPI does not decide whether you are vulnerable. Every listed SAPI runs the vulnerable engine. It decides how easily the known paths are reached, which is why PHP ranked them.

How each SAPI runs PHP

The first three columns describe architecture. The last column comes from PHP's advisories.

SAPIWhere PHP runsProcess reuseAccountWhat PHP's advisories say
Apache mod_phpInside each Apache childChildren serve many requestsApache User and GroupHighest risk; RCE validated for both CVEs
PHP-FPMSeparate worker pool under an FPM masterpm.max_requests default 0, no recyclingPer pool user109618 likely by source analysis, no complete exploit; 109619 validated under conditions
php-cgiPer request (CGI) or persistent (FastCGI)None in plain CGIWhatever launches itListed as affected by php.net; not analysed separately
FrankenPHPInside the Go and Caddy server, on a PHP thread poolThreads serve many requests; worker mode keeps the app in memoryThe service account109618 likely, not validated; 109619 no early trigger in default config, which PHP says is not immunity
LiteSpeed (LSAPI)Separate lsphp processesLSAPI_MAX_REQS default 10000LiteSpeed config109618 likely, not validated; 109619 conditional candidate

Apache mod_php

With mod_php, the PHP engine is loaded into Apache itself, usually with the prefork MPM, which is what PHP's validation used. Apache's documentation calls prefork "a non-threaded, pre-forking web server": a parent keeps a pool of child processes ready, and each child serves one request after another. The parent usually starts as root to bind port 80, and children run as the account set by User and Group. On a server with many sites, every site's PHP usually shares that one account.

The php-src advisories give three reasons this design is most exposed:

  1. Cleanup runs even after a failed start. The CVE-2026-109618 advisory says "Apache mod_php unconditionally invokes the request shutdown procedure, even when request startup has failed."
  2. Request bodies are parsed during startup. That parsing can hit the memory limit before any application code runs. The advisory says RCE was validated over HTTP against mod_php with default limits and several prefork workers, and that more workers change the number of attempts, not whether the issue exists.
  3. Directory configuration applies before request startup. The CVE-2026-109619 advisory says this lets Apache act on stale state early, and reports confirmed RCE against mod_php.

Help Net Security put it plainly: "Setups using mod_php face the greatest danger."

PHP-FPM

PHP-FPM runs PHP outside the web server. nginx, or Apache with mod_proxy_fcgi, forwards requests over FastCGI to a pool of workers. php.net says pools can run workers with their own uid, gid, chroot and environment, and each pool must set a user. The pm setting picks static, dynamic or ondemand children, and pm.max_requests sets how many requests a child serves before respawning. Its default of 0 means indefinitely.

What PHP's advisories say:

  • CVE-2026-109618. FPM exits a child when request startup fails, so the half-built state is not consumed. Risk remains after normal startup. Source analysis supports FPM being susceptible, but no complete FPM exploit has been shown.
  • CVE-2026-109619. FPM is conditionally exposed, for example when a frontend passes PHP_VALUE or PHP_ADMIN_VALUE parameters that trigger an early diagnostic. The session lifecycle path was validated on FPM with a normal dynamic pool and a suitable application operation.

Red Hat adds that standard RHEL deployments use FPM, and that because FPM workers exit on early startup failures, code execution there is hard in practice and typically needs non-default behaviour. Red Hat also says installing the fixed release is "the only complete fix." So FPM is less exposed to the demonstrated paths. It is not safe unpatched.

php.net's FPM page also warns that php-fpm must never be reachable from an untrusted network, because a client that can open a FastCGI connection controls request configuration. That predates PHP2Shell, but it matters more when a flaw is sensitive to early configuration.

php-cgi

php-cgi covers two setups. In plain CGI, the web server starts a new PHP process per request and it exits afterwards. In FastCGI mode, the binary stays running and serves many requests under a manager such as Apache's mod_fcgid or IIS on Windows, and reports itself as cgi-fcgi. Apache's mod_fcgid docs say PHP FastCGI processes exit after 500 requests by default, controlled by PHP_FCGI_MAX_REQUESTS.

php.net lists php-cgi as affected, but the GitHub advisories do not analyse it separately, so there is no official per-path rating. Do not read that as lower risk. Tenable's FAQ notes php-cgi was exploited before through CVE-2024-4577, which we covered in CVE-2024-4577 in PHP-CGI. Huntress lists php-cgi and php-cgi.exe among worker processes to watch.

FrankenPHP

FrankenPHP is a Go application server built on Caddy, with PHP embedded in the same process. Its docs say classic mode serves connections from a fixed pool of PHP threads, much like FPM's pools, with one thread pool per Caddy instance shared across sites. Worker mode boots the application once and keeps it in memory, so globals and static state carry over between requests.

What PHP's advisories say:

  • CVE-2026-109618. Source analysis suggests FrankenPHP is likely susceptible to attacks that do not depend on the application, because, like mod_php, it calls request shutdown unconditionally. Not validated.
  • CVE-2026-109619. The early trigger was not shown in the default configuration, but PHP warns this does not make FrankenPHP immune.

The advisories do not single out worker mode. One practical point matters for patching: FrankenPHP's docs say its Linux binaries are statically linked and include PHP, so the PHP inside changes only when you install a new FrankenPHP build or image.

LiteSpeed (LSAPI)

LiteSpeed and OpenLiteSpeed run PHP as separate lsphp processes over LSAPI, a protocol php.net describes as similar to FastCGI. LiteSpeed's docs say that with LSAPI_CHILDREN above 1 (default 35), a ProcessGroup parent forks children that share an opcode cache, while a value of 1 starts and stops processes as needed. LSAPI_MAX_REQS sets how many requests a child serves before exiting, default 10000.

What PHP's advisories say: for CVE-2026-109618, LSAPI is modelled on FPM, and application-specific exploits are likely but not validated. For CVE-2026-109619, LSAPI is a conditional candidate because it can apply some environment INI values early, though no equivalent trigger was established.

What "highest risk" does and does not mean

It ranks known paths, not the bug. The CVE-2026-109619 advisory says, "There is no complete generic configuration mitigation." The CVE-2026-109618 advisory likewise calls patching the only complete mitigation. Both apply to every SAPI.

Moving off mod_php is not a patch. Switching to FPM mid-incident takes time and testing, and still leaves you on the vulnerable engine. Patch first. Plan any migration as a separate change.

The account differs, not the outcome. Huntress and the advisories describe impact as code execution with the web worker's privileges. With mod_php on a shared server, that is usually one account for every site. With FPM, each pool can run as its own user, which can limit what one compromised worker reaches. The Hacker News also reports the flaws can expose worker memory, which may hold tokens and keys from other requests.

How to tell which SAPI you run

Do not guess from the web server name. nginx can front FPM, php-cgi or other FastCGI servers. Apache can run mod_php, FPM through mod_proxy_fcgi, or php-cgi through mod_fcgid. Ask the PHP that serves web traffic.

1. Ask PHP through the web

php.net documents PHP_SAPI values including apache2handler, cgi-fcgi, fpm-fcgi and litespeed, and warns that mod_php may report apache2handler rather than apache. Place a temporary file in a web root you control, request it, then delete it.

<?php
// sapi-check.php: delete immediately after use
echo PHP_SAPI, ' ', PHP_VERSION, PHP_EOL;
curl -s https://your-site.example/sapi-check.php
rm /var/www/your-site/sapi-check.php
Value printedMeaning
apache2handlerApache mod_php
fpm-fcgiPHP-FPM
cgi-fcgiphp-cgi in FastCGI mode
litespeedLiteSpeed lsphp
Anything elseCheck your server's docs; FrankenPHP is not in php.net's list

php -v reports the CLI, which may be a different package from the one serving requests.

2. Check from the host

# Is mod_php loaded, and which MPM is active?
apachectl -M 2>/dev/null | grep -i php
apachectl -V 2>/dev/null | grep -i 'Server MPM'

# PHP related processes, their user and start time
ps -eo user,pid,lstart,cmd | grep -E '[p]hp-fpm|[p]hp-cgi|[l]sphp|[f]rankenphp|[h]ttpd|[a]pache2'

# Does the web server hand PHP to FastCGI?
grep -RnE 'fastcgi_pass|proxy:fcgi|FcgidWrapper' /etc/nginx /etc/apache2 /etc/httpd 2>/dev/null

# FPM pool accounts and recycling
grep -RhE '^\s*(user|pm|pm\.max_requests)\s*=' /etc/php/*/fpm/pool.d/ /etc/php-fpm.d/ 2>/dev/null

If apachectl -M shows a php_module line, mod_php is loaded even if your main sites use FPM. A forgotten vhost on mod_php is still the highest risk path on that box.

For FrankenPHP, its docs show frankenphp php-cli running a script with the embedded PHP:

printf '%s\n' '<?php echo PHP_VERSION, PHP_EOL;' > /tmp/v.php
frankenphp php-cli /tmp/v.php

For LiteSpeed, php.net says lsphp -v showing litespeed in parentheses means an LSAPI build. On Windows with IIS, look for php-cgi.exe processes and run php-cgi.exe -v in each PHP folder IIS points at.

How to restart each SAPI so the patched binary loads

A package upgrade replaces files on disk. Running processes keep the old code until they exit.

  1. Apache mod_php. PHP lives inside Apache. Apache's docs say that on both graceful and plain restart signals the parent keeps running and only children are replaced. Since prefork children are forked from that parent, we recommend a full stop and start, then a check that no process maps a replaced PHP library.
sudo systemctl restart apache2     # Debian, Ubuntu
sudo systemctl restart httpd       # RHEL family
for p in $(pgrep -f 'apache2|httpd'); do grep -l 'libphp.*(deleted)' /proc/$p/maps 2>/dev/null; done
  1. PHP-FPM. Restart every FPM service. Debian style systems run one per PHP version.
systemctl list-units --type=service | grep -i fpm
sudo systemctl restart php8.3-fpm  # repeat per version; RHEL: php-fpm
ps -o pid,lstart,cmd -p "$(pgrep -o -f 'php-fpm: master')"
  1. php-cgi. Restart whatever manages it: Apache for mod_fcgid, the systemd unit for a standalone spawner, or the IIS application pools that use PHP (or iisreset in a maintenance window).

  2. FrankenPHP. Install the new binary or pull the new image, then restart the service or replace the container. FrankenPHP's docs describe a worker restart endpoint (curl -X POST http://localhost:2019/frankenphp/workers/restart), but it restarts workers inside the running process. Because PHP is embedded in that process, our reading is that it will not load a new binary, so use a full restart here.

sudo systemctl restart frankenphp          # systemd service
kubectl rollout restart deployment/<name>  # Kubernetes
  1. LiteSpeed and OpenLiteSpeed. LiteSpeed's docs give this sequence to signal lsphp to restart and then restart the server. For detached mode, the WebAdmin console also has a "Restart Detached PHP Processes" action.
sudo touch /usr/local/lsws/admin/tmp/.lsphp_restart.txt
sudo systemctl restart lsws
  1. Long-running PHP outside the web server. Queue workers, schedulers and supervisor daemons hold the old engine too. Restart them.

For proof that every process picked up the new code, see the needrestart and dnf needs-restarting checks in find and patch every PHP runtime after PHP2Shell.

What would have helped

Practices any team can use

  • Record the SAPI in your inventory. "mod_php, prefork, runs as www-data" or "FPM, three pools, one user each" lets you rank work in minutes when an advisory ranks SAPIs.
  • Retire stray mod_php. Mixed estates, mostly FPM with a few old vhosts on mod_php, are where the highest risk path hides.
  • Separate accounts per site. FPM pools with their own user limit what one compromised worker can reach.
  • Keep FastCGI listeners private. php.net's FPM warning applies to every FastCGI style SAPI.
  • Write the restart step per SAPI. "Restart PHP" means different commands for Apache, FPM, IIS, FrankenPHP and LiteSpeed. Name each one in the runbook.
  • Watch what web workers spawn. Huntress suggests alerting when php-fpm, php-cgi, httpd or apache2 launch shells or download tools.

Where Safeguard fits

Safeguard would not have prevented PHP2Shell, and it does not restart processes or change your SAPI. It helps you find where PHP ships and make the change.

  • Find PHP in images and SBOMs. safeguard scan --image <name> builds an SBOM of a container image, including OS packages such as PHP, and matches components against vulnerability databases. safeguard scan --dir . covers repositories and safeguard scan --sbom <file> covers SBOMs you receive. Results land as JSON under ./output.
  • See which projects ship a version. The platform shows which projects contain a given component and version.
  • Gate the next build. Policies and gates evaluated in the platform can stop an image that still carries a vulnerable PHP package.
  • Make the change with approval. The Safeguard CLI's assistant can edit a Dockerfile base tag, rebuild and run tests, asking before each command under the default permission mode. The desktop and phone apps can send that work to a computer running the CLI and approve each command.

Where it does not help: it does not know which SAPI a host uses, cannot see PHP on hosts you never scan, may not see a statically linked FrankenPHP binary as a PHP package, and cannot prove a worker restarted. Reachability does not lower priority here, because the flaw sits in the runtime below your code.

Zero-Day Discovery and Gold

  • Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. PHP core is a runtime, not a package in those ecosystems, so it is outside that scope.
  • Gold Open Source (gold.safeguard.sh, free, no login) offers searchable CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, plus a free read API and a CI gate GitHub Action.
  • Gold Registry (registry.safeguard.sh, paid) provides hardened drop-in components and container images held to zero exploitable critical or high CVEs, SLSA L3+ and Sigstore signed, with evidence pages. An artifact loses its badge on regression.

The habit that matters: pull patched, signed components from a curated source, and be able to rebuild and redeploy fast.

To try Safeguard, download it at https://safeguard.sh/download and read the CLI docs at https://docs.safeguard.sh/docs/cli .

Common mistakes

  • Reading "highest risk for mod_php" as "FPM is safe". FPM is susceptible to one CVE and validated for the other under conditions.
  • Assuming nginx means FPM. Ask PHP with PHP_SAPI.
  • Missing a mod_php vhost on an FPM server. Check apachectl -M on every Apache host.
  • Reloading instead of restarting. Apache's parent survives both restart signals, and FrankenPHP's worker restart stays inside the same process.
  • Restarting only one FPM. Restart the service for every installed PHP version.
  • Forgetting IIS application pools. php-cgi.exe keeps running until its pool recycles.

FAQ

Why does PHP rate mod_php as the highest risk?

According to the php-src advisories, mod_php runs request cleanup even when startup fails, parses request bodies during startup, and applies Apache directory configuration before startup. Together these let a demonstrated attack fire before application code runs. RCE was validated on mod_php for both CVEs.

Is PHP-FPM safe from PHP2Shell?

No. FPM's habit of exiting a child after a failed start blocks one sequence, and no complete FPM exploit was shown for CVE-2026-109618. CVE-2026-109619 was validated on FPM under certain conditions. Red Hat says installing the fixed release is the only complete fix.

Is php-cgi affected?

Yes, php.net lists it. The GitHub advisories do not analyse it separately, so there is no official per-path rating. Patch it and restart whatever manages it.

Is FrankenPHP worker mode more exposed than classic mode?

PHP's advisories do not compare them. They say FrankenPHP is likely susceptible to CVE-2026-109618 by source analysis, and that the absence of an early CVE-2026-109619 trigger in the default configuration is not immunity.

How do I patch FrankenPHP?

Its docs say Linux binaries are statically linked and include PHP, so you need a FrankenPHP build or image carrying the fixed PHP. When we checked its releases page on 21 October, the latest release was dated 16 October and did not mention the PHP fixes. Check the PHP version the binary reports.

Does a graceful Apache restart load the new PHP?

Apache's docs say the parent keeps running on both graceful and plain restart signals. With mod_php the engine lives in Apache, so do a full stop and start and check that no process maps a deleted PHP library.

Does pm.max_requests=1 protect FPM?

The CVE-2026-109619 advisory lists it as an option where performance allows. It stops a worker serving more than one request, but it does nothing for CVE-2026-109618 and is not a fix.

Fix it without waiting for upstream

In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

These demos show real Safeguard sessions on other examples. None of them shows PHP2Shell.

The CLI demo fixes a lodash finding: it reads the advisory, asks before each command, edits the version, runs install and tests with approval, and pushes nothing. For PHP2Shell, you would scan your PHP image with safeguard scan --image, then ask the assistant to move the Dockerfile base tag to a fixed PHP such as an 8.3.36 tag and rebuild, approving each command.

Click through it at your own pace, or open it full screen.

The desktop app demo shows a CLI session in the conversation list, a request for work on your computer, picking the folder, then Approve or Deny on each command and file edit, ending with an upgrade done and tests passing. Here, the same flow fits a repo holding your Dockerfiles: you approve the base image change and the rebuild step by step.

Click through it at your own pace, or open it full screen.

The phone demo is a real Android session that sends work to a computer running the CLI and approves a command with Allow once, Allow for turn or Deny. On a night like 21 October, an on call engineer away from a laptop could approve a rebuild step this way. Host restarts stay with whoever owns those hosts.

Click through it at your own pace, or open it full screen.

Quick check

Your site runs nginx. Which SAPI does it use?

You cannot tell from nginx alone. Request a temporary file that echoes PHP_SAPI: fpm-fcgi means PHP-FPM and cgi-fcgi means php-cgi in FastCGI mode.

You patched FrankenPHP's image and called the worker restart endpoint. Are you done?

Not for this fix. That endpoint restarts workers inside the running process, and PHP is embedded in it. Replace the container or restart the service, then confirm the PHP version the binary reports.

Sources

Last reviewed: 21 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.