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.

3D team beneath a lightbulb, for the PHP-FPM master process

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.

3D group of users around a secured server, for PHP-FPM worker pools

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.

3D gauge dial, for how many PHP-FPM workers run

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).

3D wireless signal with a padlock, for the socket or address PHP-FPM listens on

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.

3D network of laptops and servers, for nginx passing requests to PHP-FPM

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
3D network of connected machines, for Apache passing requests to PHP-FPM

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. 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 it php-fpm. Note every line: each one is a separate PHP build to update and restart.

  2. 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. 3. Install the fixed packages

    # Debian and Ubuntu
    sudo apt update
    sudo apt upgrade
    
    # RHEL and its rebuilds
    sudo dnf update --security

    Distribution 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. 4. Test the configuration

    sudo php-fpm8.4 -t        # Debian and Ubuntu
    sudo php-fpm -t           # RHEL and its rebuilds

    The 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. 5. Restart FPM, fully

    sudo systemctl restart php8.4-fpm   # repeat for each branch you run
    sudo systemctl restart php-fpm      # RHEL and its rebuilds

    A restart cuts off requests that are in progress at that moment. On a busy site, choose a quiet minute, but do it today.

  6. 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 -W or rpm -q against 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.

The full PHP2Shell advisory

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.

Moving Apache from mod_php to PHP-FPM

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.

3D rocket illustration above the call to find every exposed PHP server

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.