PHP 7.4 and 8.3 are both on the server, php -v reports 8.3, and the cron job for the old application fails anyway.
The command line, the FPM service and individual vhosts each decide independently which version they use. Changing one leaves the other two exactly as they were.
Several versions side by side is not an exotic setup, either. As soon as you source PHP from Sury it is the normal state: the packages carry their version in the name and do not interfere.
Which versions are installed
ls /etc/php/
systemctl list-units 'php*-fpm*' --no-pager
The first lists the configuration directories, which is every installed version. The second shows which FPM services are actually running — an installed version need not be started.
The three layers
Three places decide, and they do it independently of each other:
| Layer | Decides | Controlled by |
|---|---|---|
| Command line | php in the terminal, cron jobs, Composer | update-alternatives |
| FPM service | which service runs at all | systemctl per version |
| Vhost | which version a site uses | socket in the web server |
The three are independent. A server can use 8.3 on the command line while one site runs on 7.4 and another on 8.2.
The command-line default
Debian manages this through update-alternatives. Interactively:
sudo update-alternatives --config php
Or set directly:
sudo update-alternatives --set php /usr/bin/php8.3
Check with php -v. Every version also stays reachable under its full name:
php7.4 -v
php8.3 -v
php8.3 /path/script.php rather than php /path/script.php. The job then survives someone changing the default later. This is one of the most common reasons for tasks that fail unnoticed for months.Besides php, other alternatives manage their own tools, phar and phar.phar among them. update-alternatives --get-selections | grep php lists them all.
FPM services
Each version brings its own service and its own socket:
/run/php/php7.4-fpm.sock
/run/php/php8.3-fpm.sock
Start them per version:
sudo systemctl enable --now php8.3-fpm
sudo systemctl status php7.4-fpm
ls -la /run/php/ shows which sockets actually exist. Only those can be addressed from the web server — a socket for a version whose service is not running simply is not there.
The configuration is separate too. Every version has its own files:
/etc/php/7.4/fpm/php.ini
/etc/php/7.4/fpm/pool.d/www.conf
/etc/php/8.3/fpm/php.ini
/etc/php/8.3/fpm/pool.d/www.conf
A setting you change in 8.3 does not apply to 7.4.
A version per vhost in Apache
Inside the vhost you decide which socket PHP files go to:
<VirtualHost *:443>
ServerName old.example.com
DocumentRoot /var/www/old
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost/"
</FilesMatch>
</VirtualHost>
A second vhost on the same machine then points at php8.3-fpm.sock. The required modules have to be enabled:
sudo a2enmod proxy_fcgi setenvif
sudo systemctl reload apache2
As long as mod_php is enabled it claims every PHP file, and the vhost setting never takes effect. Disable it first:
sudo a2dismod php8.3
sudo systemctl restart apache2
A version per vhost in nginx
Here the decision sits in fastcgi_pass:
server {
server_name old.example.com;
root /var/www/old;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
}
For the next server block you only swap the socket. Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
Separate FPM pools instead of one shared one
By default a pool called www serves every site on a version. For separate applications a pool per site is worth it, because they then run as different system users and cannot read each other's files.
To do that, add a second file beside www.conf in the same directory, say /etc/php/8.3/fpm/pool.d/customer1.conf, with its own pool name, its own user, its own group and its own listen socket. Then restart the service and point the vhost at the new socket.
A compromised site then cannot reach another site's files, and a pool that jams does not drag the rest down with it.
Checking which version applies where
php -v
php8.3 -v
ls -la /run/php/
systemctl list-units 'php*-fpm*' --no-pager
For a specific site the most reliable probe is a short script calling phpinfo() that you delete immediately afterwards. Besides the version it also shows which php.ini was actually loaded.
phpinfo() script exposes paths, extensions, version numbers and configuration values. Do not park it under a name like info.php, and do not leave it behind.Getting rid of a version
sudo systemctl disable --now php7.4-fpm
sudo apt purge 'php7.4-*'
Check first that no vhost and no cron job still points at it, or the site serves an error page instead of PHP afterwards. /etc/php/7.4/ survives apt remove and only disappears with purge.
Troubleshooting
| Problem | Cause and fix |
|---|---|
php -v differs from the website's version | Command line and vhost are separate. update-alternatives --config php only affects the terminal. |
| Cron job uses the wrong version | The job says php instead of php7.4. Write the version out. |
| The vhost setting has no effect | mod_php is still enabled and intercepts every PHP file. Disable it with a2dismod. |
| The web server reports a missing socket | That version's FPM service is not running. Start it with systemctl enable --now. |
A php.ini change does nothing | The wrong version's file was edited, or the wrong branch of cli and fpm. |
| An extension is missing in one version only | Extensions install per version: php7.4-gd in addition to php8.3-gd. |
| Error page after removing a version | A vhost still points at the deleted socket. |
Frequently asked questions about multiple PHP versions
Can PHP 7.4 and 8.3 run at the same time?
Yes. The packages carry the version in their names, have separate configuration directories and their own FPM services. They do not interfere.
How do I switch the PHP version on the command line?
With sudo update-alternatives --config php interactively, or --set php /usr/bin/php8.3 directly. That only affects the terminal and cron jobs, not your websites.
How do I give a single site a different version?
Through the FPM socket. In Apache with SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost/" inside the vhost, in nginx through fastcgi_pass in the matching location block.
Why does nothing change after editing the vhost?
Usually mod_php is still enabled in Apache. It claims every PHP file before the vhost setting applies. Disable it with a2dismod php<version> and restart Apache.
Do I have to install extensions for every version separately?
Yes. php8.3-gd only applies to 8.3; 7.4 additionally needs php7.4-gd. Packages without a version number land on the default version.