Different applications often need different PHP versions: an older project may only be tested on PHP 8.2 while a new one targets PHP 8.4. Because each PHP-FPM version runs as its own service with its own socket, one server can host all of them and Nginx can send each site to the right one. In this tutorial you will install PHP 8.2 and 8.4 on Ubuntu 24.04 (they can also coexist with the distribution's PHP 8.3), configure one Nginx site per version and control which version the php command runs.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • Nginx installed (sudo apt install nginx).
  • Two domain or subdomain names pointing to the server. This tutorial uses legacy.your_domain for the PHP 8.2 site and app.your_domain for the PHP 8.4 site.

Step 1 - Adding the ondrej/php repository

Ubuntu 24.04 only packages PHP 8.3. Other versions come from the ppa:ondrej/php repository, maintained by Ondřej Surý, who also maintains PHP in Debian. It is the de facto standard source for multiple PHP versions on Ubuntu, but it is a third-party repository, not an official Ubuntu one.

Install the tool that manages PPAs and add the repository:

sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php

Press ENTER when prompted. The command imports the repository's signing key and refreshes the package index.

Verify that the new versions are available:

apt-cache policy php8.2-fpm php8.4-fpm
php8.2-fpm:
  Installed: (none)
  Candidate: 8.2.x-1+ubuntu24.04.1+deb.sury.org+1
  ...
php8.4-fpm:
  Installed: (none)
  Candidate: 8.4.x-1+ubuntu24.04.1+deb.sury.org+1
  ...

Check which PHP versions are still maintained on php.net before you pick one. Versions past their end of life get no security fixes, so run them only as long as you need to migrate the application.

Step 2 - Installing several PHP-FPM versions

Package names include the version number, so each version and its extensions install independently. Install PHP 8.2 and PHP 8.4 with the same set of extensions:

sudo apt install php8.2-fpm php8.2-mysql php8.2-curl php8.2-mbstring php8.2-xml php8.2-zip php8.2-gd php8.2-intl
sudo apt install php8.4-fpm php8.4-mysql php8.4-curl php8.4-mbstring php8.4-xml php8.4-zip php8.4-gd php8.4-intl

Always install extensions with the versioned name (php8.2-mysql). The unversioned meta package php-mysql only installs the extension for the newest version in the repository.

Each version runs its own systemd service. Check that both are active:

systemctl is-active php8.2-fpm php8.4-fpm
active
active

Each service creates its own socket in /run/php/:

ls /run/php/*.sock
/run/php/php-fpm.sock
/run/php/php8.2-fpm.sock
/run/php/php8.4-fpm.sock

php-fpm.sock is a symlink managed by update-alternatives that points to one of the versions. Don't use it in site configurations: always reference the versioned socket so a site never changes PHP version by accident.

The configuration of each version is completely separate, under /etc/php/8.2/ and /etc/php/8.4/. A change to /etc/php/8.2/fpm/php.ini only affects PHP 8.2.

Step 3 - Creating one Nginx site per PHP version

Create a document root for each site with a page that prints the running version:

sudo mkdir -p /var/www/legacy.your_domain /var/www/app.your_domain
echo '<?php echo "PHP " . PHP_VERSION . PHP_EOL;' | sudo tee /var/www/legacy.your_domain/index.php /var/www/app.your_domain/index.php > /dev/null

Create the server block for the PHP 8.2 site:

sudo nano /etc/nginx/sites-available/legacy.your_domain
server {
    listen 80;
    listen [::]:80;
    server_name legacy.your_domain;

    root /var/www/legacy.your_domain;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

Create the server block for the PHP 8.4 site:

sudo nano /etc/nginx/sites-available/app.your_domain
server {
    listen 80;
    listen [::]:80;
    server_name app.your_domain;

    root /var/www/app.your_domain;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }
}

The only difference that matters is the fastcgi_pass line. Enable both sites, test the configuration and reload Nginx:

sudo ln -s /etc/nginx/sites-available/legacy.your_domain /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app.your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 4 - Verifying each site's PHP version

Request both sites. If DNS is not set up yet, the Host header lets you test directly against the server:

curl -H "Host: legacy.your_domain" http://127.0.0.1/
curl -H "Host: app.your_domain" http://127.0.0.1/
PHP 8.2.x
PHP 8.4.x

Each site is served by its own PHP-FPM version. To move a site to another version later, install the matching phpX.Y-fpm and extensions, change fastcgi_pass to that version's socket, run sudo nginx -t and reload Nginx. The switch takes effect immediately and does not affect other sites.

Step 5 - Choosing the default PHP version for the command line

The php command is also managed by update-alternatives. After installing new versions it usually points to the highest one. Check it:

php -v | head -n 1
PHP 8.4.x (cli) (built: ...) (NTS)

To change the system default interactively, run:

sudo update-alternatives --config php
There are 2 choices for the alternative php (providing /usr/bin/php).

  Selection    Path             Priority   Status
------------------------------------------------------------
* 0            /usr/bin/php8.4   84        auto mode
  1            /usr/bin/php8.2   82        manual mode
  2            /usr/bin/php8.4   84        manual mode

Press <enter> to keep the current choice[*], or type selection number:

Or set it directly without the menu:

sudo update-alternatives --set php /usr/bin/php8.2

Changing the default only affects the php command. It does not change which version Nginx uses for any site.

In scripts, cron jobs and deployment tools, don't rely on the default. Call the versioned binary for the application instead, so a later change of the default cannot break it:

php8.2 /var/www/legacy.your_domain/artisan schedule:run
php8.4 /usr/bin/composer install --working-dir=/var/www/app.your_domain

The first line assumes a Laravel application and the second one Composer installed at /usr/bin/composer; adapt them to your own commands.

Step 6 - Managing and removing versions

Every version is managed as its own service, so reload or restart only the one you changed:

sudo systemctl reload php8.2-fpm

List all installed PHP packages to see which versions and extensions you have:

dpkg -l 'php*' | grep ^ii | awk '{print $2}'

When no site uses a version anymore, remove it with all its extensions. First check that no Nginx configuration still points to its socket:

grep -r "php8.2-fpm.sock" /etc/nginx/sites-enabled/

If the command prints nothing, remove the packages:

sudo apt purge 'php8.2*'
sudo apt autoremove

Troubleshooting

  • 502 Bad Gateway on one site only. That site's PHP-FPM version is not running or the socket name is wrong. Check systemctl status php8.2-fpm and compare fastcgi_pass with the files in /run/php/.
  • Call to undefined function mysqli_connect() or similar. The extension is installed for another version. Install the versioned package, for example sudo apt install php8.2-mysql, and reload php8.2-fpm.
  • E: Unable to locate package php8.2-fpm. The PPA was not added or the package index was not refreshed. Run sudo add-apt-repository ppa:ondrej/php and sudo apt update again.
  • A setting in php.ini has no effect. You edited the file for another version or for the CLI. PHP-FPM reads /etc/php/X.Y/fpm/php.ini; the CLI reads /etc/php/X.Y/cli/php.ini.

Conclusion

You installed PHP 8.2 and 8.4 side by side on Ubuntu 24.04, pointed each Nginx site to its own PHP-FPM socket and set the default version for the command line. Next, give each site its own PHP-FPM pool and Linux user for isolation, add HTTPS to both sites with Certbot, and plan the migration of older applications before their PHP version reaches end of life.