Ask three people which PHP version a server runs and you can get three different answers, all of them correct. On Debian and Ubuntu, the command-line PHP you type in SSH, the PHP-FPM service behind Nginx or Apache, and the Apache module are separate programs, each with its own php.ini and its own list of enabled extensions. This guide shows how to check each one, how to confirm what a website really uses, and how to read the package version so you know whether security fixes are installed.
Three ways PHP runs on one server
PHP calls these interfaces SAPIs. In the Debian packaging, each SAPI is built to read its own directory under /etc/php/<version>/:
| SAPI | Used by | Main config (PHP 8.4 example) | Package |
|---|---|---|---|
| CLI | SSH sessions, cron jobs, Composer, Artisan, WP-CLI | /etc/php/8.4/cli/php.ini | php8.4-cli |
| FPM | Nginx, or Apache with proxy_fcgi | /etc/php/8.4/fpm/php.ini plus pools in /etc/php/8.4/fpm/pool.d/ | php8.4-fpm |
| Apache module | Apache with mod_php | /etc/php/8.4/apache2/php.ini | libapache2-mod-php8.4 |
Extensions are switched on per SAPI through the conf.d directory next to each php.ini, which is why an extension can be present for the CLI and missing for the website. Debian also gives the CLI its own php.ini with memory_limit = -1 (no limit), while the web SAPIs keep the upstream 128M default, so a script that works over SSH can still run out of memory in the browser.
Check the command-line PHP
php -v # version of the "php" command
php --ini # which php.ini and conf.d files were loaded
php -m # loaded extensions
php --ri opcache # settings of one extension
readlink -f "$(command -v php)"
update-alternatives --display phpOn Debian-family systems /usr/bin/php is managed by update-alternatives. When several versions are installed, the command points at the highest-priority version unless someone fixed it by hand, so php -v can report 8.5 while the website still runs 8.4. From PHP 8.5, php --ini=diff also lists the settings that differ from the built-in defaults.
Check PHP-FPM
FPM has one binary and one systemd unit per version (php-fpm8.4 and php8.4-fpm.service, php-fpm8.3 and php8.3-fpm.service, and so on):
ls /usr/sbin/php-fpm*
systemctl list-units 'php*-fpm.service'
php-fpm8.4 -v
php-fpm8.4 -m
sudo php-fpm8.4 -t # test the FPM configuration
ls -l /run/php/php-fpm8.4 -m reads the FPM php.ini and conf.d, so it reflects what web requests get. The -t option checks /etc/php/8.4/fpm/php-fpm.conf and the pool files without restarting anything. The sockets in /run/php/ show which pools are listening; the default Debian pool listens on /run/php/php8.4-fpm.sock.
Next, confirm which socket the web server actually uses:
grep -R "fastcgi_pass" /etc/nginx/sites-enabled/
grep -R "SetHandler" /etc/apache2/sites-enabled/ /etc/apache2/conf-enabled/
ls /etc/apache2/mods-enabled/ | grep -i phpDebian also maintains a generic /run/php/php-fpm.sock through update-alternatives. It follows the highest-priority FPM version that is running, so a site configured with that generic path can change PHP version on its own when another version is installed. The last command shows whether the Apache module (mod_php) is enabled at all.
Confirm what the website really runs
The one check a misread config file cannot fool is asking PHP from inside a web request. Create a small file with an unguessable name, open it once, and delete it:
NAME="check-$(openssl rand -hex 8).php"
echo '<?php echo PHP_VERSION, " ", php_sapi_name(), " ", php_ini_loaded_file();' | sudo tee /var/www/example.com/public/$NAME
# open https://example.com/<that name> once, then:
sudo rm /var/www/example.com/public/$NAMEThrough FPM you should see fpm-fcgi; through the Apache module, apache2handler. If you need the full picture, phpinfo() prints every setting and extension, but the PHP manual notes that it also outputs server and environment information and all request data (environment, GET, POST, cookie and server variables). A forgotten phpinfo.php therefore hands anyone who finds it your paths, versions, module list and sometimes environment variables holding secrets. Use a random name, delete it the same minute, and never commit one to a code base.
Check the installed package and its patch level
dpkg -l 'php*' | grep '^ii'
apt policy php8.4-fpm
dpkg-query -W -f='${Package} ${Version}\n' 'php8.4*'The upstream number from php -v is only half the story; the package version tells you which security fixes are installed. Debian 13 (trixie) ships PHP 8.4, and its security updates have followed upstream point releases: DSA-6574-1 on 21 October 2026 brought 8.4.27-1~deb13u1. Debian 12 (bookworm) ships PHP 8.2, which now receives fixes from the Debian LTS team; DLA-4848-1 brought 8.2.35-1~deb12u1 the same day. Ubuntu works differently: Ubuntu 24.04 ships PHP 8.3 and Ubuntu 22.04 ships PHP 8.1 with fixes backported, so the package keeps the old upstream number (for example 8.3.6-0ubuntu0.24.04.13). Compare Ubuntu packages against Ubuntu Security Notices, not against php.net. Upstream support for PHP 8.1 ended on 31 December 2025, so fixes for it now depend entirely on the distribution. If you added a third-party repository for extra PHP versions, apt policy also shows which origin each installed package came from.
Common pitfalls
- Editing the wrong php.ini. A change in
/etc/php/8.4/cli/php.inidoes nothing for the website. Edit the file for the SAPI you mean, then restart that service:sudo systemctl restart php8.4-fpmorsudo systemctl restart apache2. - Installing an extension and not restarting FPM. The CLI sees a new package such as
php8.4-intlat once; running FPM workers do not until the service restarts. - Composer checking the wrong PHP. Composer runs under the CLI. Run
composer check-platform-reqswith the same version the site uses, for examplephp8.4 "$(command -v composer)" check-platform-reqs. - Two FPM versions running with the web server still pointed at the old one after an upgrade.
- Old phpinfo files left in web roots by earlier troubleshooting. Search for them:
sudo grep -rl "phpinfo" /var/www --include='*.php', and review each hit before deleting.
What you should do
- For each site, record the PHP version and SAPI from a request-level check, then delete the check file.
- Compare
php -mwithphp-fpm8.x -mso cron jobs and web requests load the same extensions. - Point every site at a versioned FPM socket rather than the generic one.
- Check
apt policyfor your PHP packages against your distribution's advisories at least monthly. - Plan ahead for end-of-life dates on php.net: PHP 8.2 security support ends on 31 December 2026, and PHP 8.1 is already end of life.
Sources
- php.net/manual/en/features.commandline.options.php
- php.net/manual/en/function.phpinfo.php
- php.net/manual/en/function.php-sapi-name.php
- php.net/manual/en/function.php-ini-loaded-file.php
- php.net/supported-versions.php
- php.net/eol.php
- manpages.debian.org/trixie/php8.4-cli/php8.4.1.en.html
- manpages.debian.org/trixie/php8.4-fpm/php-fpm8.4.8.en.html
- manpages.debian.org/trixie/dpkg/update-alternatives.1.en.html
- manpages.debian.org/trixie/apt/apt-cache.8.en.html
- manpages.debian.org/trixie/dpkg/dpkg-query.1.en.html
- sources.debian.org/src/php8.4/8.4.27-1~deb13u1/debian/rules
- sources.debian.org/src/php8.4/8.4.27-1~deb13u1/debian/rules.d/prepare-fpm-pools.mk
- sources.debian.org/src/php-defaults/96/php-fpm-socket-helper
- packages.debian.org/trixie/amd64/php8.4-fpm/filelist
- security-tracker.debian.org/tracker/DSA-6574-1
- security-tracker.debian.org/tracker/DLA-4848-1
- packages.ubuntu.com/noble/php8.3-fpm
- packages.ubuntu.com/jammy/php8.1-fpm
- ubuntu.com/security/notices
- getcomposer.org/doc/03-cli.md#check-platform-reqs
Comments
0 total · 0 threads