After upgrading Ubuntu or Debian, PHP pages either download as files, show the raw source, or come back blank. Apache is still running; what broke is the link between Apache and PHP. The upgrade installed a newer PHP but left Apache pointing at the old module, or the module was removed and nothing replaced it. The fix is a few commands.
What the upgrade did#
Each Ubuntu and Debian release ships one PHP version. Ubuntu 22.04 has PHP 8.1, 24.04 has 8.3, Debian 12 has 8.2. When you upgrade the distribution, the package manager installs the new PHP but the Apache module for the old one — libapache2-mod-php8.1, say — either disappears or stays enabled while pointing at files that no longer exist.
Apache then has no working PHP handler. It does what it does with any unknown file: serves it as-is.
Which symptom you have#
| Symptom | Meaning |
|---|---|
| Browser downloads the .php file | No PHP handler loaded at all |
| Raw PHP source shown in the browser | Same, with a text content type |
| Blank white page | PHP runs but hits a fatal error, and errors are hidden |
| Apache fails to start | Config references a module file that is missing |
The first two are this article. The third is a different problem — usually a missing extension after the upgrade — and the last is covered below.
Step 1: see what is installed#
php -v # the command-line PHP
ls /etc/apache2/mods-available/ | grep php
ls /etc/apache2/mods-enabled/ | grep php
apache2ctl -M 2>/dev/null | grep php
Typical broken output: php -v says 8.3, mods-available lists php8.1.load, and the last command prints nothing. Apache knows about a module you no longer have.
Step 2: install the module for the current version#
sudo apt update
sudo apt install libapache2-mod-php8.3 # match the version php -v printed
The package name has the version in it. Use whichever php -v reported.
Step 3: disable the old module, enable the new one#
sudo a2dismod php8.1 # ignore the error if it is already gone
sudo a2enmod php8.3
sudo systemctl restart apache2
Apache only loads one mod_php at a time. If two are enabled it refuses to start, which is the fourth symptom in the table above.
Step 4: check#
echo '<?php phpinfo();' | sudo tee /var/www/html/info.php
curl -s http://localhost/info.php | grep -o 'PHP Version [0-9.]*' | head -1
sudo rm /var/www/html/info.php # do not leave this on a public server
If curl prints a version, Apache is running PHP again. Test in a browser too; a cached download response can make it look still broken. A hard refresh sorts that out.
If you use PHP-FPM instead#
Many setups run PHP as a separate FPM service rather than an Apache module. Then the failure looks like a 503 or a “Service Unavailable” page, and the fix is on the FPM side:
sudo apt install php8.3-fpm
sudo systemctl enable --now php8.3-fpm
sudo a2enmod proxy_fcgi setenvif
sudo a2disconf php8.1-fpm
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2
systemctl status php8.3-fpm
The socket path changes with the version too, so any virtual host that hard-codes /run/php/php8.1-fpm.sock needs updating to php8.3-fpm.sock.
Apache will not start#
sudo apachectl configtest
sudo journalctl -u apache2 -n 30 --no-pager
Look for a line naming a .so file it cannot find. That is a stale .load file in mods-enabled. Disable it with a2dismod, or remove the symlink by hand, and try again.
Missing extensions#
A blank page after the module is working usually means the code needs an extension the upgrade did not carry across: mysqli, mbstring, curl, gd, xml, zip. Check the error log rather than guessing:
sudo tail -n 30 /var/log/apache2/error.log
php -m | sort # what the CLI has loaded
sudo apt install php8.3-mysql php8.3-mbstring php8.3-curl php8.3-xml php8.3-zip php8.3-gd
sudo systemctl restart apache2
Note that php -m shows the command-line PHP’s extensions, which can differ from Apache’s. The phpinfo page is authoritative for what the web server sees.
Keeping several PHP versions#
If you need an older PHP for one application, the ondrej/php repository on Ubuntu provides every supported version side by side. Then update-alternatives picks the CLI default and a2enmod picks Apache’s:
sudo update-alternatives --config php
sudo a2dismod php8.3 && sudo a2enmod php8.1 && sudo systemctl restart apache2
Questions people ask#
Why did the upgrade not handle this?
Distribution upgrades replace packages but do not rewrite your Apache configuration. The enabled-modules list is your configuration, so it is left alone.
Does this apply to nginx?
nginx always uses PHP-FPM, so the FPM section applies: install the new FPM package and update the socket path in your server block.
The site works on port 80 but not over HTTPS.
That is a separate virtual host. Check the SSL host’s config for a hard-coded FPM socket path or a stale handler line.
Is mod_php or PHP-FPM better?
FPM for anything beyond a single small site. It isolates PHP from Apache, uses memory better, and lets you run different PHP versions for different sites.
Where to go next#
- PHP errors not showing — for the blank-page case.
- How to check which PHP version is running — CLI vs web server.
- PHP deployment and hosting — the wider setup.