DEV Community

Danila Vershinin for GetPageSpeed

Posted on Originally published at getpagespeed.com AI-assisted

Eleven PHP versions, two package shapes, one CentOS 7: shipping the PHP2Shell fix everywhere

The day after PHP2Shell (CVE-2026-109618 and CVE-2026-109619) went public, we shipped patched PHP 5.6, 7.0, 7.3 and 8.0 for CentOS 7. I wrote up how those first backports were ported and proven. This is the follow-up: getting from "the versions we had time for" to every PHP a CentOS 7 box can run, 5.6 through 8.4, in both of Remi's package shapes.

The real problem was the matrix, not the patch

Once the fix was ported to 5.6 and 7.0, the hard engineering was done. What was left was combinatorics:

  • 11 PHP versions (5.6, 7.0 to 7.4, 8.0 to 8.4)
  • 2 package shapes per version: Remi's base-name php-* (replaces system PHP, from remi-phpNN) and the phpNN-php-* software collection (side by side, from remi-safe)
  • a different starting point per version: a frozen EL7 build, a maintained EL8+ source, or plain upstream

Doing that as 11 separate packaging repos meant 11 copies of the same build glue drifting apart. So the per-version repos were folded into one matrix repo: a matrix.yml declares each version's source, collection name, channel and twin mode, and an assemble.sh renders each CI unit's build directory from it. The SCL twin of a version is generated from the base spec, not committed by hand. Adding 7.1 and 7.2 became a matrix.yml row plus that version's patches.

Two versions (5.6 and 8.0) keep two committed specs, because Remi's base and SCL specs for them differ by hundreds of hunks (the base builds ZTS, the collection does not). Merging them would either drop ZTS from the base shape or produce an unmaintainable spec. Two small deltas over Remi beat one big one.

Where the packages live

The channel layout ended up mirroring Remi's own, which keeps the install story uniform:

  • Base-name php-* for version NN lives in an opt-in getpagespeed-extras-phpNN channel, enabled per transaction: yum --enablerepo=getpagespeed-extras-php74 upgrade 'php-*'. It has to be opt-in, because a base-name php from another version would out-version whatever the host runs on an unscoped yum update.
  • Collections (php56-php-* to php84-php-*) live in the always-enabled main repository, like remi-safe. Their names are version-prefixed, so they can never collide with anything.

PHP 8.4 was the odd one out: Remi never built it for EL7, so there was no collection to be compatible with. It became the first collection under a GetPageSpeed prefix, /opt/getpagespeed/php84 instead of /opt/remi/php84. That is why every example uses scl enable php84 -- php -v instead of a hardcoded path: scl resolves the prefix, so the commands look the same for every version.

Runtime libraries: no Remi repository required

The Remi EL7 builds link libraries only Remi ever shipped (gd3php, oniguruma5php, libicu69/73, the phpNN-runtime metapackages). Leaving those on Remi's frozen mirror would make every fresh install depend on it. They are rebuilt from Remi's own SRPMs with identical names, SONAMEs and file lists, one release higher, so a Remi host upgrades in place and a fresh host needs only the GetPageSpeed release package.

PHP 8.4 goes further and links maintained libraries instead: OpenSSL 3.5 LTS (namespaced, never replacing the system 1.0.2), ICU 74, libzip 1.11, libpq 17 and libxml2 2.13.

Testing that it serves pages, not just that it builds

Package-level tests (phpt suite, php -m parity, the PHP2Shell probes before and after, yum downgrade back to Remi) all run on the CLI. None of them prove a channel can actually serve a request through Apache.

So there is a separate runtime suite. For every channel it starts a clean CentOS 7 container and runs four scenarios:

  • fresh: install from the repository on an empty host
  • upgrade: start from a Remi host with PECL extensions and ionCube loaded, run the documented upgrade command, fail if any extension is lost
  • scl-fresh and scl-upgrade: the same for the collection

Each scenario serves a probe page through mod_php and through PHP-FPM behind proxy_fcgi, and asserts the SAPI, the version, the build provider, sessions, gd, intl, mbstring and opcache, that the CLI and web SAPIs load the same extensions, and that the logs are clean. It runs on every change and nightly against the published repository, and a red job there is treated as a customer-visible break. On October 24 it ran 45 of 45 green.

The result

yum -y install https://extras.getpagespeed.com/release-latest.rpm
# base-name, replaces system PHP:
yum --enablerepo=getpagespeed-extras-php74 upgrade 'php-*'
# or the collection, side by side:
yum upgrade 'php74-php*'
Enter fullscreen mode Exit fullscreen mode

The same two commands work for every version from 5.6 to 8.3, and for 8.4 once its stack or collection is installed. Version-by-version details, the full fix lists and the rollback notes are in the CentOS 7 PHP2Shell guide. If you are on Ubuntu 20.04 instead, the same problem and a free fix are covered in PHP2Shell on Ubuntu 20.04.

The packages are free to install. If your CentOS 7 servers run a business, GetPageSpeed Ultra is what funds the next round of these backports.

Top comments (0)