PHP-FPM, nginx and Apache
PHP-FPM: what it is, and how to update it so the fix goes live
PHP-FPM is the PHP project's FastCGI Process Manager: a service of its own whose master process keeps pools of PHP workers ready for nginx or Apache. To fix PHP2Shell, install the fixed FPM package for your branch and then fully restart the service, because until the master and its workers restart, the old build is still answering requests.
Last reviewed against php.net, the nginx and Apache httpd documentation, Red Hat and Debian's PHP packaging.
Definition
What is PHP-FPM?
PHP-FPM (FastCGI Process Manager) is the FastCGI implementation that ships with PHP. It runs as a background service apart from the web server: a master process starts and supervises pools of worker processes, and each pool waits on a socket or port for the PHP requests that nginx, Apache or another FastCGI-capable server passes to it.
Because PHP lives in those long-running workers rather than in the web server, updating PHP means replacing the workers, and that is where most incomplete PHP2Shell fixes go wrong.
The master process
Reads php-fpm.conf, starts the worker processes and replaces them when they exit. It is the process your init system starts, stops and signals, so it is what a restart replaces.
Pools
Groups of workers with their own settings. Each pool can run as a different user, listen on a different address and carry its own PHP values. Packages ship one pool, called www.
The process manager
The pm setting decides how many workers a pool keeps: a fixed number (static), a range that grows and shrinks (dynamic, the stock setting) or none until requests arrive (ondemand).
The listen address
Each pool must listen on a Unix socket or a TCP address. php.net prefers a Unix socket when the web server is on the same host, and warns that a TCP pool accepts any address unless you restrict it.
nginx and Apache
How PHP-FPM pairs with the web server
The web server's only PHP setting is where to send requests. That is why updating nginx or Apache does nothing for PHP2Shell, and why moving a site to a new PHP branch is a one-line change on the web server side.
nginx: fastcgi_pass
nginx hands .php requests to FPM
with fastcgi_pass, which takes a
host and port or a Unix socket prefixed with
unix:. On Debian and Ubuntu each
branch's pool listens on its own socket.
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
} To move a site to another branch (the usual meaning of "nginx update PHP version"), change the socket, then test and reload. nginx's reload starts new workers with the new configuration and lets the old ones finish gracefully.
# After pointing fastcgi_pass at a different socket
sudo nginx -t
sudo nginx -s reload
Apache: mod_proxy_fcgi
Apache reaches FPM through mod_proxy_fcgi, which needs mod_proxy loaded as well. Debian's FPM package ships a ready-made snippet that sends PHP files to the branch's socket:
<FilesMatch ".+\.ph(?:ar|p|tml)$">
SetHandler "proxy:unix:/run/php/php8.4-fpm.sock|fcgi://localhost"
</FilesMatch> The package does not switch it on by itself. When Apache is installed it prints the first two of these commands; the restart then loads the new modules:
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.4-fpm
sudo systemctl restart apache2
On RHEL 9, Apache runs PHP through FPM by default, with the PHP
handler in /etc/httpd/conf.d/php.conf
and the nginx equivalent in
/etc/nginx/conf.d/php-fpm.conf.
If your Apache still loads PHP as a module, read the
Apache and mod_php guide
first: that setup carries the highest risk for these flaws.
Restart, not reload
Why the update is not live until FPM restarts
The package manager replaces files. The FPM master and its workers were started from the old files and keep running them. The PHP project's own workaround for CVE-2026-109619 asks you to restart every worker, and Red Hat goes further for the fix itself.
| What you run | What happens | Enough for PHP2Shell? |
|---|---|---|
sudo systemctl restart php8.4-fpm | systemd stops the service and starts it again: a new master process, new workers, and the binary now on disk. | Yes. This is the step the fix depends on. |
sudo systemctl reload php8.4-fpm | On Debian and Ubuntu the unit sends SIGUSR2 to the running master, which php-fpm's manual describes as a graceful reload of all workers and of FPM's configuration and binary. | No. Red Hat warns that reloading is not enough for these flaws: workers that keep running can hold affected state from earlier requests. |
Restarting nginx or Apache | Only the web server restarts. PHP-FPM runs as a separate service and carries on untouched. | No, unless PHP also runs inside Apache as mod_php. |
Installing the package and stopping there | The fixed files are on disk, but the processes already running were started from the old ones. | No, unless something restarts the service for you. Ubuntu's needrestart does so by default from 24.04; check rather than assume. |
Sources: php-fpm's manual page, Debian's and Ubuntu's php-fpm service units, systemd's systemctl manual and Red Hat's RHSB-2026-013 bulletin.
Step by step
Updating PHP-FPM on a Linux server
The examples use Debian 13's branch, 8.4. Replace it with yours: Ubuntu 24.04 ships 8.3, and servers using the Ondřej Surý repositories may run several branches at once.
-
1. List every FPM service
# Every PHP-FPM service on the machine (Debian, Ubuntu and RHEL names) systemctl list-units 'php*fpm*'Debian and Ubuntu name the service after the branch (
php8.4-fpm); RHEL calls itphp-fpm. Note every line: each one is a separate PHP build to update and restart. -
2. Record the version each one runs
# Debian and Ubuntu: one FPM binary per branch sudo php-fpm8.4 -v # RHEL and its rebuilds sudo php-fpm -v -
3. Install the fixed packages
# Debian and Ubuntu sudo apt update sudo apt upgrade # RHEL and its rebuilds sudo dnf update --securityDistribution packages carry the fix under their own version numbers, which often look older than 8.4.27 or 8.3.36. The Linux guide lists the fixed package for each Debian and Ubuntu release and explains how to read them.
-
4. Test the configuration
sudo php-fpm8.4 -t # Debian and Ubuntu sudo php-fpm -t # RHEL and its rebuildsThe test parses the configuration, including the pool files it pulls in, and exits, so a typo is caught before the restart rather than after the site goes down.
-
5. Restart FPM, fully
sudo systemctl restart php8.4-fpm # repeat for each branch you run sudo systemctl restart php-fpm # RHEL and its rebuildsA restart cuts off requests that are in progress at that moment. On a busy site, choose a quiet minute, but do it today.
-
6. Confirm
Work through the checks below for every service from step 1.
Interim measures
Where the PHP2Shell interim settings go in a pool
If a fixed package is not available yet, the PHP project lists partial measures and says plainly that none of them is complete. In FPM they belong in the pool file, so they cannot be undone by application code.
| PHP-FPM from | Pool files | Main configuration |
|---|---|---|
| Debian and Ubuntu packages | /etc/php/8.4/fpm/pool.d/ (one directory per branch) | /etc/php/8.4/fpm/php-fpm.conf |
| RHEL 9 packages | /etc/php-fpm.d/www.conf | /etc/php-fpm.conf |
| The official Docker php image | /usr/local/etc/php-fpm.d/ | /usr/local/etc/php-fpm.conf |
; Add to the pool file, e.g. /etc/php/8.4/fpm/pool.d/www.conf
; Partial measures from the PHP project's advisories. Not a fix.
; Session upload progress off; ini_set() in code cannot turn it back on
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 only: no backtraces on fatal errors
php_admin_flag[fatal_error_backtraces] = off
; Worth considering: each worker exits after a single request
pm.max_requests = 1 - php_admin_flag, not php_flag. php.net states that values set with php_admin_value or php_admin_flag cannot be overridden with ini_set(). The session.upload_progress.enabled setting cannot be changed from code in the first place, which is why the advisory says to set it in php.ini or the pool, never with ini_set().
- pm.max_requests. It counts the requests each worker serves before it is replaced. The default, 0, never replaces workers on that basis. Setting 1, which the advisory asks you to consider, stops state surviving from one request to the next in the same worker.
- Restart afterwards. Pool changes take effect when FPM starts its workers again, so finish with a full restart as in step 5.
- Limits in front of FPM. The
advisory also suggests request-size limits at the web server. In
nginx that is
client_max_body_size, 1 MB by default; requests above it get a 413 error before PHP sees them.
The rest of the interim list, including the memory and input limits the advisory discusses, is on the PHP2Shell page. If you neither the fix nor these settings can go on soon, the risk calculator walks through whether the site should stay online.
Side by side
Running several PHP-FPM versions on one server
Debian's packaging keeps every branch apart: its own package, its own service, its own socket and its own configuration tree. The Ondřej Surý repositories for Debian and Ubuntu use the same package names and make the branches installable together.
| Branch | Service | Default socket | Configuration |
|---|---|---|---|
| PHP 8.2 | php8.2-fpm | /run/php/php8.2-fpm.sock | /etc/php/8.2/fpm/ |
| PHP 8.3 | php8.3-fpm | /run/php/php8.3-fpm.sock | /etc/php/8.3/fpm/ |
| PHP 8.4 | php8.4-fpm | /run/php/php8.4-fpm.sock | /etc/php/8.4/fpm/ |
| PHP 8.5 | php8.5-fpm | /run/php/php8.5-fpm.sock | /etc/php/8.5/fpm/ |
Each branch is its own fix
Updating php8.4-fpm does nothing for sites still served by php8.2-fpm. Upgrade and restart every branch that any site uses, and stop the ones nothing uses.
The generic socket can move
Debian and Ubuntu also keep /run/php/php-fpm.sock,
an alternatives link that each running branch registers with its
branch number as priority, so the highest branch wins. A web
server pointed at it can change branch when you install or stop
one. Name the versioned socket to stay in control.
Enterprise Linux
The base packages give one PHP at a time. Remi's repository provides extra branches as Software Collections, named for example php83, alongside the default version; each collection is updated separately.
Any pool on PHP 8.1 or older is a priority: the PHP project ships no fix for those branches, so move its sites to a supported one. See PHP end of life dates.
Confirm it worked
Prove the running workers are the new ones
Two questions: did the service start again after the update, and does the website report the fixed build?
# "Active: active (running) since ..." must be later than your restart
systemctl status php8.4-fpm
# The release of the FPM binary itself
sudo php-fpm8.4 -v Ask the website itself
The command line cannot tell you what FPM serves. A two-line file in the site's document root can; delete it the moment you have read the answer.
<?php
// fpm-check.php: upload, open once in a browser, then delete it
echo PHP_VERSION, ' ', PHP_SAPI; -
The second word should be
fpm-fcgi. Anything else means the site is not on the FPM pool you updated. -
The version should be the fixed release for your branch. On a
distribution package, judge by the package version from
dpkg-query -Worrpm -qagainst the advisory, not by the number PHP prints. -
If you use FPM's status page (
pm.status_path), its start time field shows when the pool last started.
Other ways to read the live version, including WordPress Site Health, are on how to check your PHP version. Both CVEs are on CISA's Known Exploited Vulnerabilities catalogue, so if the server was exposed before you patched, also read the compromise checks.
Troubleshooting
Common PHP-FPM update problems
The site shows an error page straight after you changed branch
The web server is still sending requests to the old branch's socket, or to a service you have stopped. Point nginx's fastcgi_pass, or Apache's SetHandler, at the new socket, test the configuration and reload the web server.
The command line reports the fixed release but the site does not
The command line and FPM are separate packages and separate processes. Check the FPM package version itself, then restart the FPM service; the command line has no effect on what FPM serves.
systemctl restart fails after you edited a pool file
Run php-fpm8.4 -t (or php-fpm -t) to see the line FPM objects to. systemctl status also prints the service's most recent log lines, which usually name the file and line.
An interim setting does not seem to apply
It is probably in a different pool, or in another branch's directory. Each branch reads only its own /etc/php/X.Y/fpm/ tree. Check the value through the website, not the command line, and restart the service after every change.
Some sites on the server are fixed and others are not
Several branches, or several services, are running. List them with systemctl list-units 'php*fpm*', update each branch's FPM package and restart every one of them.
Pages slowed down after setting pm.max_requests = 1
That is the cost of starting a new worker for every request. It is an interim measure: once every branch is on a fixed release, return the pool to the value you had before.
Quick answers
PHP-FPM: questions, answered
What does PHP-FPM stand for?
FastCGI Process Manager. FastCGI is the protocol a web server uses to hand a request to a separate program; PHP-FPM is the PHP project's own implementation of it, described on php.net as its primary FastCGI implementation. It ships as part of PHP and is packaged on its own, for example as php8.4-fpm on Debian 13.
Is PHP-FPM affected by PHP2Shell?
Yes. php.net lists PHP-FPM among the affected setups. The PHP project's advisories treat it as likely susceptible to CVE-2026-109618, and validated the session upload progress route for CVE-2026-109619 against FPM as well as mod_php. Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distribution's fixed package, and restart FPM.
Do I need to restart nginx after updating PHP-FPM?
No. nginx never loads PHP; it only forwards requests to FPM's socket or port. Restart PHP-FPM instead. nginx needs attention only if you move a site to a different branch, because the fastcgi_pass line then has to name the new socket, followed by nginx -t and a reload.
Is systemctl reload enough after a PHP-FPM update?
Not for this fix. php-fpm's manual describes its reload signal as a graceful reload of all workers, but Red Hat's guidance for these CVEs rules a reload out, since long-running workers can carry affected state. Use systemctl restart, which stops and starts the whole service.
Does PHP-FPM work with Apache's event MPM?
Yes. php.net recommends PHP-FPM with mod_proxy_fcgi for modern Apache deployments precisely because it works with the event MPM, Apache's default since 2.4, and because PHP can then be restarted without restarting Apache. mod_php without a thread-safe build needs the older prefork MPM.
Should I keep pm.max_requests = 1 after patching?
Normally not. The default is 0, meaning workers are never recycled for request count, and php.net describes the setting as a way to work around memory leaks in third-party libraries. The PHP project suggests 1 as something to consider while vulnerable; once patched, go back to your usual value. Leaving session upload progress off is different: the advisory recommends that even after patching, if nothing uses it.
FPM in containers is covered in the Docker guide; every other route starts at how to update PHP.
Know your exposure
Find every PHP server you run
Patching the server you know about is the easy part. A vulnerability scan finds the forgotten staging site, the old microsite and the appliance with PHP inside, then tells you which ones are still exposed.