Two days after PHP2Shell was disclosed, I started a Zabbix PHP upgrade by reviewing PHP across our monitoring environment. On the web server, dnf check-update 'php*' returned no available updates, so before deciding anything I looked at the repository configuration.
This post covers how I scoped the review, why the update list was empty, and how I upgraded the web server from PHP 8.0.30 to 8.3.36 and verified it. The database servers and proxies needed different decisions, and I’ll cover them in the next post.
What Was Disclosed
On October 21, 2026, the PHP project released fixes for two critical remote code execution vulnerabilities, CVE-2026-109618 and CVE-2026-109619, known together as PHP2Shell. Both are rated 9.2 on CVSS v4.0.
| PHP branch | Fixed version |
|---|---|
| 8.2 | 8.2.35 |
| 8.3 | 8.3.36 |
| 8.4 | 8.4.27 |
| 8.5 | 8.5.12 |
PHP 8.1 and older did not get a new upstream release. The PHP security team published minimal source backports for 7.4, 8.0 and 8.1, and distribution vendors may ship their own backports.
CISA urged organizations to patch immediately. The next day, Zabbix published a statement saying that PHP is used by the Zabbix frontend but is a separate dependency that isn’t included in Zabbix packages. Patching depends on your PHP or operating system vendor, and internet-facing deployments come first.
Scoping the Review
The Zabbix statement helped define the scope. I checked each server’s installed PHP packages and how PHP was actually used, rather than assuming every Zabbix component needed the same changes.
Our environment has separate main, database, web and proxy servers, and only the web server runs the frontend.
- Web server: Zabbix 7.0.29 on RHEL 8.10, running the frontend through Apache and PHP-FPM. PHP 8.0.30 was installed from the RHEL AppStream. The service is only used internally, but the server can communicate outside the network, so I didn’t treat it as exempt.
- Main servers: checked, no changes made.
- Database servers and proxies: some had PHP packages installed. Those are in the next post.
On each server I looked at the installed PHP packages, whether the PHP-FPM service was running, and which processes were using PHP before touching anything.
Why dnf Found No Updates
On the web server, the check was:
dnf check-update 'php*'
It returned no PHP updates. I wasn’t ready to read that as “up to date” until I’d looked at which repositories dnf could see.
The enabled repositories included Zabbix and MariaDB. The RHEL BaseOS and AppStream repositories were both set to enabled=0, and so was the local repository for the installation media. With none of the enabled repositories carrying PHP updates, dnf had nothing that could offer one, so the empty list only told me what dnf could see.
I haven’t checked Red Hat’s own advisories for this PHP stream, so I can’t say whether Red Hat published a fix for it. Upstream, PHP 8.0 is end of life and only received a source backport rather than a release. An empty check-update can mean no update exists, no enabled repository offers one, or the branch is no longer maintained. dnf repolist shows which repositories are enabled. Whether a fix exists is a question for the vendor’s advisory.
Moving to PHP 8.3
With the RHEL repositories disabled, I needed another package source. EPEL and Remi could be installed on this server, and Remi provides PHP 8.3 as a module stream. Zabbix 7.0.29 supports PHP 8.3, so I switched to that stream.
dnf module list php
dnf module reset php
dnf module enable php:remi-8.3
dnf module list php
The goal was to see remi-8.3 [e] on the last command. Then I confirmed that the 8.3.36 packages existed:
dnf list --showduplicates php php-cli php-fpm
And before changing anything, a dry run:
dnf update 'php*' --assumeno
From the dry run I wanted to know whether PHP 8.3.36 got selected, whether dependency resolution succeeded, what new dependencies would come in, and whether any Zabbix package or other core package would be removed. Only after that did I run the real update:
dnf update 'php*'
Checking the Result
php -v showed PHP 8.3.36, with Zend Engine v4.3.36 and Zend OPcache v8.3.36. That describes the command-line PHP, so I checked the rest of the stack too:
php-fpm -tto test the configuration, which passed.php -mto confirm the modules Zabbix relies on still loaded: bcmath, mbstring, mysqlnd, PDO, xml, gd and the rest.- Restarted PHP-FPM and checked that it was running and ready to handle connections, then checked Apache.
Then I made a real web request:
curl -k -I https://localhost/zabbix/
HTTP/1.1 200 OK
X-Powered-By: PHP/8.3.36
Set-Cookie: zbx_session=...
The response returned HTTP 200 and X-Powered-By: PHP/8.3.36, so the Zabbix frontend request was being handled by PHP 8.3.36 and not just the updated CLI binary. I also read the PHP-FPM and Apache logs for the previous 15 minutes, which showed a normal stop and start and no PHP fatal errors, and I opened the main Zabbix pages in the GUI: login, the dashboard, Problems, Hosts and Latest data.
What I’ll Do Differently
For future patching work, I’ll check repository availability and vendor advisories before treating an empty update list as a final answer. After an upgrade, I’ll verify the PHP version through an actual web request, not just the CLI binary.
The web server was the simple case. On the database servers, a dry run showed that removing PHP-FPM would have pulled in far more than PHP-FPM. That’s the next post: When Removing PHP-FPM Almost Removed Zabbix Web.
Related reading: The Alert That Never Fired is another Zabbix story where the thing that looked fine wasn’t.
Frequently Asked Questions
Does every Zabbix server need PHP?
No. Zabbix describes PHP as a separate dependency used by the frontend. In our environment only the web server ran the frontend, so that was the server where PHP had to stay and be patched. Other servers can have PHP packages installed without using them, so check what’s installed and running on each one rather than going by its role.
Why does dnf check-update show no PHP updates on RHEL?
It only reports what the enabled repositories offer. On our web server the RHEL BaseOS and AppStream repositories were disabled, so dnf had nothing to compare against. That doesn’t tell you whether a fix exists, only that dnf couldn’t see one. Run dnf repolist to see which sources dnf is using, and check the vendor’s advisory for the fix itself.
