Why
Once you have more than three servers, the question "have we updated?" no longer has a single answer. One server was missed, PHP-FPM was not restarted on another, and on a third PHP runs in a container. Below is a check that answers the question as a list.
The main trap: the version number lies
On Ubuntu, Debian, and AlmaLinux, fixes are backported into the old version without changing its number. On a patched server, php -v shows 8.3.6, the same number as on a vulnerable one. Comparing version numbers is useless: you need the package changelog.
The check script
Put the list of servers in a file named hosts.txt, one per line, and run:
#!/usr/bin/env bash
set -uo pipefail
while read -r host; do
[ -z "$host" ] && continue
printf '%-28s ' "$host"
ssh -o ConnectTimeout=5 -o BatchMode=yes "$host" '
php_v=$(php -v 2>/dev/null | head -1 | awk "{print \$2}")
ngx_v=$(nginx -v 2>&1 | grep -oE "[0-9]+\.[0-9]+\.[0-9]+")
fixed=no
if zgrep -lq "CVE-2026-109618" /usr/share/doc/php*/changelog.Debian.gz 2>/dev/null; then
fixed=yes
elif rpm -q --changelog php-cli 2>/dev/null | grep -q "CVE-2026-109618"; then
fixed=yes
fi
fpm_start=$(ps -eo lstart,cmd 2>/dev/null | grep -m1 "php-fpm: master" | cut -c1-24)
echo "php=${php_v:-none} patched=$fixed nginx=${ngx_v:-none} fpm_since=${fpm_start:-none}"
' 2>/dev/null || echo 'unreachable'
done < hosts.txtHow to read the output
| Field | Meaning |
|---|---|
patched=yes | the vulnerability numbers were found in the package changelog, so the fix is installed |
patched=no | either the server was not updated, or PHP was built manually or comes from a third-party source |
fpm_since | start time of the PHP-FPM master process; if it is earlier than the update, the old code is still in memory |
nginx | 1.30.6 and newer in the stable branch means the October set is fixed |
What to do with the result
- First, servers with
patched=noand port 80 or 443 open to the internet. - Then servers that have the patch but whose PHP-FPM started before the update. They only need a restart.
- Separately, containers: the script does not see them, so check them with
docker exec.
Preventing a repeat
Run the check from cron once a day and send the output to yourself. A silent server and an updated server look the same until you ask them.
Frequently asked questions
Why can't I just compare PHP version numbers to find vulnerable servers?
Because a backport does not change the version number. Comparing numbers gives a false alarm on patched servers and false reassurance where PHP is built manually.
Does the PHP and nginx version audit script need root?
No. Reading changelogs is available to an ordinary user.