Your server is running two PHP versions (and one of them is lying)

I ran into a classic PHP mess the other day. A Symfony app was behaving perfectly fine from the command line — no warnings, no drama, nothing flagged as broken — and then the moment I loaded it in the browser, it blew up with this:
Undefined constant "XML_PI_NODE"It's the kind of error that sends you chasing ghosts: something deep in the app, a flaky extension, a weird dependency mismatch buried in Composer. What it does not immediately scream is "your server is running two different PHP versions depending on how you access it." But that was exactly what was happening. CLI was on one PHP version, Apache was on another, and even though both were running on the same box against the same files, the two paths were serving code through completely different runtimes. From the terminal everything looked healthy. From the browser, things started breaking in confusing ways.
Why "PHP version" isn't really one thing
On Linux, "PHP version" is rarely a single value. The CLI binary can be on one version, the Apache module on another, and PHP-FPM on a third, all sitting quietly side by side on the same machine. Upgrade one of them and forget about the rest, and the box keeps limping along fine until something in the code hits behavior that differs between versions. That's usually when the weird errors start — and the most maddening part is that they don't show up consistently. The app isn't really "broken." It's only broken down one execution path.
The first thing I always check now
Any time PHP works in the terminal but not in the browser, I start with the same two commands. The first tells me what CLI is using:
php -vThat's half the picture. The other half is what the web server is actually running, and the quickest way to see it is still the oldest trick in the book — drop a temporary phpinfo() file into the web root:
<?php phpinfo();Open it in the browser and compare the version it reports with what php -v gave you. If the two numbers don't match, there's nothing else to investigate yet — you've already found the problem, and the rest of the debugging is just pinning down which part of the stack is out of sync.
If you're running mod_php
If Apache is loading PHP directly through mod_php, check which module it currently has active:
apache2ctl -M | grep phpYou can also look at what's installed on the system more broadly:
dpkg -l | grep libapache2-mod-phpBetween those two outputs, it usually becomes obvious whether Apache is still wired up to an older PHP module that nobody remembered to disable after the upgrade.
If Apache is proxying to PHP-FPM
If you're on PHP-FPM instead, the Apache module isn't your problem at all — the socket path is. Look at your site config and find the line that points Apache at PHP-FPM. It's usually something like:
/run/php/php8.x-fpm.sockThat version number in the socket name really matters. It's very easy to install a newer FPM package thinking you've upgraded PHP everywhere, while Apache is still happily talking to the old socket.
Why the error message wasted my time
The most irritating thing about this whole class of bug is that PHP never fails in a way that points at the real problem. It doesn't say, "CLI is on 8.5, Apache is on 8.3, please sort your life out." It just throws side effects at you — a missing constant here, a removed function there, some extension behaving differently, random incompatibilities that look for all the world like application bugs but are really environment bugs. And if you don't know to look at the environment first, you can burn hours debugging the wrong layer.
That's exactly what happened to me here. It wasn't Symfony. It wasn't Composer. It wasn't the cache. It wasn't some mystical XML bug buried in a library. Apache was simply running a different PHP version than the terminal, and every other lead I chased was a dead end.
The actual fix
Once the mismatch is confirmed, the fix is almost embarrassingly simple: pick one PHP version and make every layer use it. If Apache is still loading an old mod_php, turn it off and turn the right one on:
a2dismod php8.3
a2enmod php8.5
systemctl restart apache2If you're on PHP-FPM, install the version you actually want and update the site config so Apache points at the matching socket:
apt install php8.5-fpmThen restart both services so the new pairing actually takes effect:
systemctl restart php8.5-fpm
systemctl restart apache2After that, the weird error disappears and everything becomes boring again, which is exactly what you want from infrastructure.
How to keep this from happening again
The easiest way to avoid all of this is to be deliberate about your PHP setup. If you use PHP-FPM, use it everywhere and stop mixing it with mod_php. If you use mod_php, know which module is currently enabled and make sure it matches the CLI version. And if you keep multiple PHP versions installed on the same box (which is fine, and sometimes necessary), keep track of which service is pointing at which — because having multiple versions isn't really the problem. Forgetting that different parts of the stack can quietly drift apart from each other: that's the problem.
Takeaway
If PHP works from the terminal but not from the browser, don't dig into the app first. Start by comparing the PHP versions — check php -v, check phpinfo(), check your Apache module or your FPM socket path. There's a good chance the app is fine and the server is just the one lying to you.
Running into a weird PHP or Apache mismatch? Send me the details and I'll take a look.