Skip to main content

Auditing PHP and nginx Versions Across a Server Fleet with One Script

Security · 27.10.2026 · 3 min read

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

How to read the output

FieldMeaning
patched=yesthe vulnerability numbers were found in the package changelog, so the fix is installed
patched=noeither the server was not updated, or PHP was built manually or comes from a third-party source
fpm_sincestart time of the PHP-FPM master process; if it is earlier than the update, the old code is still in memory
nginx1.30.6 and newer in the stable branch means the October set is fixed

What to do with the result

  1. First, servers with patched=no and port 80 or 443 open to the internet.
  2. Then servers that have the patch but whose PHP-FPM started before the update. They only need a restart.
  3. 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.

Was this article helpful?
← Back to Knowledge Base Ask Support