The PHP CLI version tells you nothing certain about PHP-FPM. They are separate binaries, often from separate packages, and the FPM process may have started months before the last update. For PHP2Shell (CVE-2026-109618 and CVE-2026-109619, disclosed 21 October 2026) you need the version of the code that is running in memory and serving requests.

This guide walks from the web server config to the socket, to the process, to the binary on disk, and finally to proof that the workers were replaced.

Prerequisites and applicable environments {#prerequisites}

  • Root or sudo on the host.
  • Nginx or Apache that proxies to PHP-FPM, or a standalone FPM listener.
  • Knowledge of the distro family. Commands below are labeled for Debian and Ubuntu, and for RHEL, AlmaLinux, and Rocky Linux.

The guide does not cover mod_php. For that, see the Apache mod_php check. FPM is not safe from PHP2Shell. It is a different evidence level from internet-facing mod_php, and it still needs the fixed build.

IMPORTANT

Fixed upstream releases are 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Ubuntu 24.04 noble php8.3-* packages are fixed at 8.3.6-0ubuntu0.24.04.13 (USN-8948-1). PHP 8.1 and older have no upstream updates, so only a vendor package or a migration fixes those.

How the pieces connect {#how-it-connects}

Nginx or Apache fastcgi_pass Socket or port ss -xlpn Master process PID, start time /proc/PID/exe binary on disk

Step-by-step procedure {#procedure}

Step 1. Check the service state {#step-systemctl}

READ-ONLY

Debian and Ubuntu (unit names include the branch, such as php8.3-fpm):

systemctl list-units --type=service | grep -i 'php.*fpm'
systemctl status php8.3-fpm --no-pager

RHEL, AlmaLinux, Rocky Linux (the unit is php-fpm, or a stream-specific name on some builds):

systemctl list-units --type=service | grep -i 'php.*fpm'
systemctl status php-fpm --no-pager
Illustrative output● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Active: active (running) since Mon 2026-09-14 03:12:44 UTC; 5 weeks 3 days ago
Main PID: 1042 (php-fpm8.3)

Note the Active: ... since line. A date earlier than your update is already a warning sign.

Step 2. List FPM processes with start times {#step-ps}

READ-ONLY

ps -eo pid,ppid,lstart,args | grep -E '[p]hp-fpm|[p]hp.*fpm'
Illustrative output1042 1 Mon Sep 14 03:12:44 2026 php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)
1051 1042 Mon Sep 14 03:12:44 2026 php-fpm: pool www
1052 1042 Mon Sep 14 03:12:44 2026 php-fpm: pool www

The master process is the one with the config path in its title and a parent PID of 1 (or the init system). The lstart column is the process start time. A master that predates the package update is still the old code, even if the file on disk is new.

Step 3. Find the listening socket {#step-ss}

READ-ONLY

sudo ss -xlpn | grep -i fpm
sudo ss -tlnp | grep -i fpm

The first command lists Unix sockets. The second lists TCP listeners, for pools that use 127.0.0.1:9000 or similar.

Illustrative outputu_str LISTEN 0 4096 /run/php/php8.3-fpm.sock 28931 * 0 users:(("php-fpm8.3",pid=1042,fd=8))

The pid= field names the process that owns the socket. That PID should match the master from Step 2.

Step 4. Ask the binary behind the process {#step-proc-exe}

READ-ONLY

sudo ls -l /proc/1042/exe
Illustrative outputlrwxrwxrwx 1 root root 0 Oct 22 08:11 /proc/1042/exe -> '/usr/sbin/php-fpm8.3 (deleted)'
WARNING

If the target ends with (deleted), the package manager replaced the file after the process started. The running master is still the old code. Restart the service. This is the clearest proof that a package install without a worker restart is incomplete.

If there is no (deleted) marker, ask that exact binary for its version:

READ-ONLY

sudo /usr/sbin/php-fpm8.3 -v
sudo /usr/sbin/php-fpm8.3 -t

-v prints the version of the file on disk. -t tests the configuration and does not start the service. Use the full path from /proc/PID/exe, not a bare php-fpm.

Step 5. Map the package {#step-package}

READ-ONLY

Debian and Ubuntu:

dpkg -S /usr/sbin/php-fpm8.3
dpkg-query -W -f='${Package}\t${Version}\n' php8.3-fpm

RHEL, AlmaLinux, Rocky Linux:

rpm -qf /usr/sbin/php-fpm
rpm -q php-fpm

Compare the package version with the vendor advisory. On Ubuntu 24.04 noble, php8.3-* at 8.3.6-0ubuntu0.24.04.13 is the fixed build (USN-8948-1), even though the upstream base reads 8.3.6.

Map Nginx to the right pool {#nginx-mapping}

A host may run several pools and several branches. Find which one a given site uses.

Step 6. Read the Nginx config {#step-nginx-config}

READ-ONLY

sudo nginx -T 2>/dev/null | grep -nE 'server_name|fastcgi_pass'
Illustrative output (example hostnames)12: server_name app.example.test;
31: fastcgi_pass unix:/run/php/php8.3-fpm.sock;
58: server_name legacy.example.test;
77: fastcgi_pass unix:/run/php/php8.1-fpm.sock;

Step 7. Match the target to the socket {#step-match}

Site (example) fastcgi_pass target Socket owner PID Branch Action
app.example.test /run/php/php8.3-fpm.sock 1042 8.3 Check package version, restart if stale.
legacy.example.test /run/php/php8.1-fpm.sock 1180 8.1 No upstream fix. Vendor backport or migration.
api.example.test 127.0.0.1:9000 1302 Unknown Use ss -tlnp and /proc/1302/exe.

For Apache with proxy_fcgi, look for SetHandler "proxy:unix:..." or ProxyPassMatch lines:

READ-ONLY

Debian and Ubuntu:

sudo apachectl -S 2>/dev/null | head -n 30
sudo grep -RInE 'proxy:unix|proxy:fcgi|SetHandler' /etc/apache2/

RHEL, AlmaLinux, Rocky Linux:

sudo httpd -S 2>/dev/null | head -n 30
sudo grep -RInE 'proxy:unix|proxy:fcgi|SetHandler' /etc/httpd/

Each pool has its own socket or port. Inventory every pool file under /etc/php/*/fpm/pool.d/ (Debian family) or /etc/php-fpm.d/ (RHEL family), because a pool can run under a different master than the one you checked first.

Prove the workers were replaced {#prove-replacement}

Updating the package is not enough. Confirm that the old master is gone.

CONFIG

Test the configuration before you restart:

sudo /usr/sbin/php-fpm8.3 -t

SERVICE

Restart during a window you have approved. A restart drops in-flight requests. A reload (systemctl reload) can leave the old master in place on some setups, so use a restart for a security update. See restart PHP-FPM safely.

Debian and Ubuntu:

sudo systemctl restart php8.3-fpm

RHEL, AlmaLinux, Rocky Linux:

sudo systemctl restart php-fpm

READ-ONLY

Then re-run Steps 2 to 4:

ps -eo pid,ppid,lstart,args | grep -E '[p]hp-fpm'
sudo ls -l /proc/$(pgrep -o -f 'php-fpm: master')/exe
EXPECTED

A new master PID, a start time after the update, and an /proc/PID/exe target without (deleted). The package version matches the vendor advisory or a fixed upstream release (8.2.35, 8.3.36, 8.4.27, or 8.5.12).

LIMIT

This guide proves the version and replacement. It does not prove the host is clean. If the host was reachable while vulnerable, use the checking guidance and incident response procedures.

Decision table {#decision-table}

Finding Meaning Action
Master started before update Old code in memory Restart, then recheck.
/proc/PID/exe shows (deleted) Binary replaced on disk Restart, then recheck.
Package matches advisory, fresh start time Fixed and running Record, verify.
Branch 8.1 or older, no vendor advisory No upstream fix Backport request or migration. Mitigate temporarily.
Binary not owned by a package Unverified build Replace with a vendor package or rebuild.
LEGACY

PHP 8.1 and older receive no upstream updates. Vendor backports are possible, and minimal source backports exist for 7.4, 8.0, and 8.1 only. Do not assume a 5.6 patch exists. WAF and ini mitigations are temporary, not complete fixes.

Troubleshooting {#troubleshooting}

Symptom Likely cause What to do
ss shows no FPM socket Pool listens on TCP, or service is stopped Use ss -tlnp, check systemctl status.
Two masters on one host Two branches installed Check each with /proc/PID/exe. Map each to its sites.
php-fpm -t fails after update Pool or ini file references a removed module Read the error, fix the config, re-test before restart.
Version looks fixed, start time old Package updated, service not restarted Restart and recheck.
Permission denied on /proc/PID/exe Not root Re-run with sudo.
Socket path in Nginx does not exist Wrong branch name or pool renamed Compare with ss -xlpn and the pool listen = setting.
TROUBLESHOOT

If the restart fails, run journalctl -u php8.3-fpm --no-pager -n 50 (Debian family) before changing anything. A failing restart leaves the site without PHP, so keep your rollback plan open. See the rollback plan.

Completion checklist {#completion-checklist}

  • Every FPM unit on the host is listed with its start time.
  • Each web server fastcgi_pass or proxy_fcgi target is mapped to a socket and PID.
  • /proc/PID/exe was read for every FPM master, and no target shows (deleted).
  • The binary was queried by full path with -v, not through a bare php-fpm name.
  • Package version matches the vendor advisory or a fixed upstream release.
  • Configuration tested with php-fpm -t before the restart.
  • Service restarted, and the new master start time is after the update.
  • Every pool file is accounted for, including pools on other branches.
  • Results recorded in the ticket with real hostnames, PIDs, and start times.

Questions

Why can php-fpm show the old version after I updated the package?

The master process started before the update still runs the old code in memory. Restart the service. A package install without a worker restart is incomplete.

How do I know which FPM binary serves my site?

Map the web server's fastcgi_pass or proxy_fcgi target to a socket or port, find the process that listens on it with ss, and read /proc/PID/exe for that process.

Sources checked