mod_php vs PHP-FPM: what changes when a bug lands in core
Published · updated
The same PHP version can be served two ways, and the difference decides how much of your server a core bug reaches. With two unauthenticated RCEs patched this week, that difference is worth ten minutes of checking.
The structural difference
mod_php embeds the interpreter in the Apache process. Every Apache child that handles a request is a PHP process: it reads your php.ini, runs as the Apache user, and shares the web server's privilege level. One configuration, one user, no separation between the web server and the application.
PHP-FPM is a separate master process with worker pools. Apache or nginx proxies to it over a socket. Each pool can run as its own user, with its own memory limit, its own ini values and its own process ceiling. A bug in PHP then executes inside a worker that has the pool's permissions, not the web server's.
| mod_php | PHP-FPM | |
|---|---|---|
| Process model | inside Apache | separate master, pooled workers |
| User | web server user | per-pool user |
| Isolation between sites | none | per pool, if configured |
| Memory ceiling | per Apache child | per pool |
| Opcode cache | shared by the server | per pool |
Why the PHP team calls mod_php the most dangerous
In its advisory for CVE-2026-109618 and CVE-2026-109619 the PHP project lists mod_php as the riskiest configuration. The reason is visible in the table above: the request is processed inside the web server process, before your application code and with the web server's identity. Code execution there is code execution as Apache — and on a shared host that user may read other sites' files.
FPM does not make the bug go away. It is listed as affected as well, and the second CVE's two-request variant is sometimes easier on FPM because workers are long-lived and reused. What FPM changes is the containment: a compromised pool is a compromised pool, not the whole web server, provided pools are separated by user.
How to tell which one a site uses
- The header.
X-Powered-By: PHP/8.3.29without a separateServerproduct often means mod_php; FPM behind nginx usually showsServer: nginxand the same PHP header. - The status page. A reachable
/statusreturning “pool: www, process manager: dynamic” is FPM. More on that page → - FrankenPHP and LiteSpeed announce themselves in the headers and change the picture: LiteSpeed with
lsphpis a separate process, FrankenPHP embeds into a Go server. - On the server.
apachectl -M | grep phpshowingphp_moduleis mod_php;grep -r 'proxy_pass.*php\|fastcgi_pass' /etc/nginxis FPM.
Switching to FPM, in outline
This is a maintenance task, not an emergency one — do not migrate a live server on the day a critical patch drops. Patch first, migrate later.
- Install the FPM package for the branch you already run, and create one pool per site with its own user and socket.
- Point the web server at the socket. Apache:
SetHandler proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost. nginx:fastcgi_pass unix:/run/php/php8.3-fpm.sock;plus the standardfastcgi_params. - Move ini settings into the pool file where they were per-directory
.htaccessvalues — FPM ignores.htaccessentirely. This is where migrations break. - Test uploads, large forms and rewrite rules on staging, then flip the vhost and keep the mod_php config commented for a rollback.
After the switch, re-run an outside check: the server banner changes, and the version may become visible or disappear depending on the new ini.
Questions
Is PHP-FPM immune to the October 2026 RCEs?
No. Both CVEs affect FPM as well. FPM reduces the blast radius of a successful exploit through per-pool users and process separation; it does not prevent the exploit.
Can I tell mod_php from FPM without server access?
Usually, from the combination of Server and X-Powered-By headers, or from a reachable FPM status page. When both headers are stripped, the answer is a guess — say so rather than assume.
Does FPM use less memory than mod_php?
Typically yes for many sites on one server, because pools set their own ceilings and the web server does not carry the interpreter. The savings come from configuration, not from the mechanism.
We are on a shared host — does any of this apply?
Ask the host which handler your sites use. On shared hosting you cannot switch it yourself, and the answer also tells you whether your neighbours share your PHP process.