Videa Blog

Multiple PHP versions, the easy way

Michael Moravec  

Always wanted to try or run your application with a different PHP version without breaking everything else? Why not, there is a way to run multiple versions in parallel!

Basics

There's a lot of use cases when one would need multiple PHP versions side by side. As an example, assume we have one legacy application and one modern application. The legacy one runs only on PHP 5.x whereas the modern one runs only on PHP 7.x. Virtually impossible and disjunctive setup you'd say, right? Before you start thinking about setting up second server with new PHP, consider the option of having two PHP versions side by side and letting your application choose one.

What do we need

Debian Stretch

We'll use Debian Stretch for this purpose. Yes, at the time of writing, it's still an unreleased version, already in freeze though. But it's going to have native support for co-installable PHP versions, so we'll use it for convenience. You'll see later that it'd be eventually also possible to achieve this even with currently stable Jessie, although not with the native PHP package it distributes (php5). Or you can use Ubuntu as well, it's based on Debian after all. :-)

NGINX

Of course, we can suffer with the old friend Apache, but why would we? NGINX performs much better and is way easier to configure with PHP FPM. Also considering other features, i.e. HTTP/2, multi-certificate setup etc., there's really no show-stopper.

PHP with co-installability support

This is probably the trickier part. Luckily for us, starting with Debian Stretch there will be a new infrastructure for PHP packages that handles versioning natively. No need to mess with the source code or modifying Debian packages themselves!

Putting it all together

Install NGINX

Installing NGINX is as simple as running this command:

$ apt-get install nginx

This will install NGINX with default modules and configuration.

Installing PHP 7.0

Install PHP 7.0 from Debian archive. This will be (sadly) the default version in Stretch, 7.1 came out too late to squeeze into Stretch's timeline. Do this using the following command:

$ apt-get install php7.0-cli php7.0-fpm

Notice the different pattern of the package name. Older Debian installations used simply php5 whereas newer infrastructure uses phpX.Y. This is the obvious part that efficiently allows us to use multiple PHPs in parallel. With this structure, you can install each of the minor versions next to each other.

Installing PHP 5.6

Here's the catch. Debian only offers a single PHP version in the official repository. Fortunately there are packages directly from a maintainer of Debian's PHP packages, Ondřej Surý. Visit his page about packaging to learn more. (There is also a PPA repository in case you'd rather use Ubuntu instead of Debian.)

We'll now add his repository (as well as enable HTTPS for APT and register the APT key):

$ apt-get install apt-transport-https
$ curl https://packages.sury.org/php/apt.gpg | apt-key add -
$ echo 'deb https://packages.sury.org/php/ stretch main' > /etc/apt/sources.list.d/deb.sury.org.list
$ apt-get update

Now that we have the repository added, we can install the packages from there:

$ apt-get install php5.6-cli php5.6-fpm

This will install PHP 5.6 in parallel to PHP 7.0 installed earlier. We can check this is true by simply running:

$ php7.0 -v
PHP 7.0.15-1 (cli)
$ php5.6 -v
PHP 5.6.30-5+0~20170223133422.27+stretch~1.gbp1ee0cb (cli)

Note that for conviniency there is also a php command provided by alternatives (which defaults to the newest version):

$ php -v
PHP 7.0.15-1 (cli)

You can switch the default version using update-alternatives, just run the following command and pick the version you prefer:

$ update-alternatives --config php

Configuring PHP

Configuration is stored in versioned locations as well. Additionally the configuration is separate for each SAPI. Same applies to PHP modules so you don't have to worry about incompatible modules between versions.

We are looking for FPM configuration. PHP 7.0 FPM configuration is stored in /etc/php/7.0/fpm and PHP 5.6 in /etc/php/5.6/fpm. Each FPM instance consists of multiple pools. Ideally each site/project should have its separate pool, but that's out of scope of this article, so we'll just use the default pool called www. Open /etc/php/7.0/fpm/pool.d/www.conf and look for the listen option. It should equal /run/php/php7.0-fpm.sock or similar. Now do the same for 5.6, it should contain the same with just 5.6 instead of 7.0. Note that it could also be a bind address, i.e. IP address with port (which is performance-wise more suitable for production than sockets).

Configuring NGINX

NGINX configuration is stored inside /etc/nginx. There are multiple files and directories, here's what we will need to know:

Finally remove anything in /etc/nginx/sites-enabled, we don't want any default configuration to clutter with our setup.

Example setup

Now that we have everything ready, let's create two virtual hosts. For simplicity we'll just run them on a different port so we don't have to worry with setting up the hostnames.

Site with PHP 7.0

First, create folder for our new site and just add a phpinfo() there:

$ mkdir /var/www/site-with-php7.0
$ echo -e '<?php\nphpinfo();' > /var/www/site-with-php7.0/index.php

Now create a simple virtual host with this content. Put the following into /etc/nginx/sites-available/site-with-php7.0:

server {
    listen 8870 default_server;
    listen [::]:8870 default_server;
    server_name _;
    root /var/www/site-with-php7.0;
    index index.php;
    location / {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock; # adjust for the listen setting discussed above
    }
}

Site with PHP 5.6

We'll do just the same for 5.6, except we'll change the port, root directory and FastCGI backend:

mkdir /var/www/site-with-php5.6
echo -e '<?php\nphpinfo();' > /var/www/site-with-php5.6/index.php

Put the following into /etc/nginx/sites-available/site-with-php5.6:

server {
    listen 8856 default_server;
    listen [::]:8856 default_server;
    server_name _;
    root /var/www/site-with-php5.6;
    index index.php;
    location / {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php5.6-fpm.sock; # adjust for the listen setting discussed above
    }
}

Enable these sites:

$ ln -s ../sites-available/site-with-php5.6 /etc/nginx/sites-enabled
$ ln -s ../sites-available/site-with-php7.0 /etc/nginx/sites-enabled

Reload NGINX and we're done:

$ systemctl reload nginx.service

Testing everything out

We should now test that our setup works, shouldn't we?

Testing site with 7.0

Head over to your browser and open http://localhost:8870/. You should see the output of phpinfo() telling you that you are running PHP 7.0.

Testing site with 5.6

Now do the same for 5.6 and open http://localhost:8856/. You should be seeing PHP 5.6.

Conclusion

The article should shed some of the myths about the needs and (im)possibility of running multiple PHP versions. You now know how simple and straightforward such setup is and can deploy it right away. It's of course also possible to install PHP 7.1 or 5.5, both are available in the aforementioned repository, configuration would be equivalent. You can also use this setup on Ubuntu systems, everything is same except that you'll just do it the Ubuntu-way and use the mentioned PPA repository.

Complete example with Docker

You can also find this example in the following GitHub Gist. If you would like to try it yourself, simply clone the Gist, build the image and run it locally:

$ git clone https://gist.github.com/08bb58780344603ad253a9e3b0552eb0.git /tmp/multiphp
$ docker build -t multiphp /tmp/multiphp
$ docker run --rm -P multiphp

Now just visit http://localhost:8870/ and http://localhost:8856/ respectively to see the result!