I'm Ty Wells, and I build Seatrial. Most of the security problems I care about aren't clever exploits. They're boring: a runtime that stopped getting fixes a while ago, and nobody noticed because the app still works. PHP is a common case, because a PHP app can run happily for years on a version that upstream no longer patches.

This guide shows you how to answer one question with confidence: does the PHP version I'm running still get security fixes? You don't need any tool beyond a terminal and a browser.

Why "it still works" tells you nothing

PHP releases are organised into branches (8.1, 8.2, 8.3 and so on). Each branch gets a period of active support, with bug and security fixes. Then it gets a period of security-only fixes. After that it reaches end of life, and the PHP project stops releasing fixes for it entirely.

Nothing breaks on the end-of-life date. Your site keeps serving pages. The only difference is that when a new vulnerability is found, no fix will ship for your branch. Nothing tells you that this has happened, so you have to check.

The PHP project has changed the length of these support windows before, so don't rely on a rule of thumb you read somewhere, including here. The official source is the supported versions page at php.net: https://www.php.net/supported-versions.php. It lists each branch with its active support and security support end dates.

Step 1: Find the version you actually run

This sounds trivial, but it's where most people go wrong. Your laptop, your CI, and your production server can all run different PHP versions. Check the one that serves real traffic.

On the server, from a shell:

php -v

Be careful: the command-line PHP and the PHP that your web server uses (PHP-FPM, mod_php) can be different installs. To check the FPM binary, try:

php-fpm -v
# or, on many Debian/Ubuntu systems:
php-fpm8.2 -v
ls /etc/php/

If /etc/php/ contains several version folders, you may have more than one PHP installed. Look at your web server config (the fastcgi_pass socket in nginx, or the enabled module in Apache) to see which one is wired up.

From the outside, with response headers:

curl -sI https://your-site.example | grep -i -E 'x-powered-by|server'

If expose_php is on, you'll see something like X-Powered-By: PHP/8.1.2. That's useful for you, and also useful for anyone scanning for old versions. Turning expose_php = Off in php.ini hides the header, which is reasonable. It doesn't patch anything, though. An old version with a hidden header is still an old version.

On shared or managed hosting: check the hosting control panel. Most let you pick a PHP version per site. Write down what's selected, not what you assume.

In containers: the version is in your Dockerfile's base image.

FROM php:8.1-fpm

That line pins the branch. If the tag has no patch number, you get whatever was latest for that branch when the image was last built. That might be months ago if you haven't rebuilt.

Step 2: Check what your project declares

Your repo usually says which PHP version it expects, and that can differ from what's deployed. Look in composer.json:

"require": {
    "php": "^8.1"
},
"config": {
    "platform": {
        "php": "8.1.0"
    }
}

require.php is the range your code claims to support. config.platform.php tells Composer to resolve dependencies as if that version were installed, even if your machine runs something newer. A forgotten platform setting can quietly keep your dependencies locked to what an old PHP can handle. Also check platform files like .platform.app.yaml, app.yaml, runtime.txt or similar files your host uses. They often pin the runtime too.

If the declared version and the deployed version disagree, find out why before you change anything.

Step 3: Compare the branch to the official support table

Take the branch (the first two numbers, like 8.1) and look it up on https://www.php.net/supported-versions.php. You're in one of three states:

  • Active support: getting bug and security fixes.
  • Security fixes only: still patched for security issues, but your end date is approaching. Plan the upgrade now.
  • End of life: no more fixes from the PHP project. Any branch in the 7.x series and earlier is in this state upstream.

If you want this in a script, endoflife.date publishes the same kind of data as JSON:

curl -s https://endoflife.date/api/php.json

It's a handy community-maintained source. If it ever disagrees with php.net, trust php.net.

Step 4: Check the patch level, not just the branch

A supported branch doesn't help if you're running an early release of it. 8.2.0 and the latest 8.2.x are both 8.2, but only one has the security fixes released since. Compare your full version number against the newest release for your branch on https://www.php.net/downloads and read the changelog at https://www.php.net/ChangeLog-8.php. Security fixes are usually labelled with CVE identifiers there, so you can see what you're missing.

Step 5: Account for distro backports

This is the main exception to everything above. Linux distributions like Debian, Ubuntu and Red Hat Enterprise Linux often keep an older PHP version number and backport security fixes into their own packages. So php -v might print a version that looks old, while the package is actually patched by the vendor. It can also go the other way. A distro release that has itself reached end of support stops getting those backports.

If your PHP came from your OS package manager, check two things:

  1. The package's exact version string, for example with apt-cache policy php8.1 or rpm -q php.
  2. The vendor's security tracker or support lifecycle page for your OS release. Confirm both that the OS release is still supported and that your package version includes the fixes you care about.

The upstream PHP version number alone can't settle this. The vendor's tracker can.

Step 6: Write down "unknown" honestly

Sometimes you can't determine the version, because headers are hidden, the host is opaque, or the build is custom. Record that as unknown, not as fine. An unknown runtime is a task on your list, not a clean result. This is a rule I built into Seatrial's exposure check: it reports each runtime as VULNERABLE, END OF LIFE, OK or UNKNOWN, and UNKNOWN is never reported as safe.

Step 7: Make it a recurring check

The answer you get today expires. Branches reach end of life on a schedule, and new advisories show up whenever they show up. A few low-effort habits help:

  • Put your branch's security-support end date in your calendar, with a reminder a few months ahead.
  • Subscribe to PHP release announcements on php.net.
  • Keep an eye on CISA's Known Exploited Vulnerabilities list and NVD for issues affecting your runtime.
  • Rebuild container images regularly so patch releases actually reach production.

I got tired of doing this by hand, so this is the part Seatrial automates. The free scanner reads what a repo declares (composer PHP version, Dockerfile, platform stack files, package files) along with server headers. A scheduled job refreshes version data from php.net, endoflife.date, CISA's KEV list and NVD/OSV. It then re-checks monitored targets and alerts through Seatrial Monitor. You can also do all of the above yourself with the steps in this guide.

Quick checklist

  • Found the PHP version that serves production traffic, not just CLI
  • Compared it to composer.json and Dockerfile declarations
  • Looked up the branch on php.net's supported versions page
  • Confirmed you're on the latest patch release for that branch
  • If using distro packages, checked the vendor's security tracker
  • Recorded anything you couldn't verify as unknown
  • Set a reminder before the security-support end date

Run the free scanner on your repo

All security monitoring guides