Skip to main content
A black and white photograph of an elephant's head.

Always run the desired PHP version when your hosting solution has multiple versions available

"Oh, you wanted *that* PHP?"

At Agaric, we perform a lot of Drupal upgrades. These very often involve transitioning away from older versions of PHP. Even when your hosting service provides multiple versions of PHP, you can still run into issues activating the appropriate one for each site: whether that's within the web server, at the command line, or via Drush (or other tools). In this blog post, we'll be providing remedies for all of these cases. Most content applies to any PHP application.

Only Section 3 is particularly Drupal/Drush specific, but still might give you a hint about running remote commands that use ssh from behind their shiny CLI.

TLDR: just give me what works, NOW (please?)

Since you asked so politely: if you are already familiar with hosting options, bash shell configuration, and Drush, this somewhat lengthy article can be summed up quite quickly, really. For those that might not be pro's in any one of these, you can take this as the quick intro, and keep reading to learn why these situations exist, as well as the detailed fix.

  1. If you are on a hosting provider, they normally will have a UI to select the desired PHP version. If you are doing your own hosting, install Ondrej Sury's PHP PPA/Apt repo, and install PHP-FPM (not mod_php). Adjust your SetHandler (Apache), or fastcgi_pass (NGINX) in each site configuration to the socket for the pool of the version of PHP you want.
  2. Configure a local ~/bin folder and place a link to the php version you want (e.g. ln -s /usr/bin/php8.1 ~/bin/php) there. Be certain ~/bin is also in your PATH.
  3. When using Drush aliases to run commands on remote instances, this still won't work until you add an env-vars: key to your site.yml file and place a PATH in there.

Jump to the "Part" below if you only need help with one or more of these.

Part 1: All things hosting (well, at least Apache or NGINX, plus FPM)

Let's start where most people do: setting up the hosting itself. In most cases, a managed hosting provider will have some way to select the appropriate version of PHP for your individual site. If that's the case - pick your target, and you should be good to go (move on to Part 2, you lucky dog)! If you do your own hosting, there will be some additional steps to take, which I'll give an overview of, and some additional resources to get you over this first hurdle.

If you are running "A-PAtCHy" web server (possible name change coming?) you will not be able to use mod_php for your PHP duties, as this method does not allow the web server to directly serve content of different virtual hosts using different versions of PHP. Instead, I recommend using PHP's "FastCGI Process Manager" service - aka FPM. This is a stand-alone service that Apache and NGINX will speak to using new-age technology from 1996 called FastCGI. It's still technically CGI, only, like, Fast (seriously, it works really well). Your web server hands off to this service with it's related FastCGI/proxy module.

The process is quite similar for both web servers, and an article over at Linode covers the basics of this method for each, but wait! Finish reading at least this paragraph before you jump over there for both a caveat emptor and then some Debian specific derivations, if you need those (the article is Ubuntu-specific). In the article, they utilize the excellent PHP resources offered by Ondřej Surý. From this PPA/Debian APT resource, you can run concurrent installations of any of the following PHP versions (listed as of this writing): 5.6, 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, and 8.2. Do keep in mind, however, that versions prior to 8.0 are [per php.net] now past their supported lifetime and no longer actively developed (see also this FAQ for more details). Debian-specific instructions for setting up this repository in apt (as opposed to Ubuntu PPA support) are also found within Ondřej's instructions. The remainder of the Linode process should still apply with that one change. OK, run along and get the PHP basics talking to your web server. If you'll be running Drupal (why wouldn't you?) then you'll want to ensure you have the version-specific php modules that it requires (this is for Drupal 9+, but links to earlier revisions also).  I'll wait...

Assuming you have now progressed as far as having both versions of PHP you need installed, and followed the article from Linode above or whatever your favorite substitute source was, you likely noticed the special sauce that connects the web server to a particular PHP FPM service. In Apache, we have: SetHandler "proxy:unix:/var/run/php/php8.0-fpm.sock|fcgi://localhost", and in NGINX flavor, it's: fastcgi_pass unix:/var/run/php/php8.0-fpm.sock; These directives point to the Unix socket as defined for in the default PHP "pool" for the particular version. For reference, these are defined in /etc/php/{version}/fpm/pool.d/www.conf and therein will look like: listen = /var/run/php/php8.1-fpm.sock. So - all that's necessary to select your PHP version for your web server is to point to whichever socket location for the version of PHP you want.

The Linode article does not go into handling multiple host names, and I won't go too deep here either as I've already navigated headlong into a bit of a scope-creep iceberg. The quick-and-dirty: for Apache, add another site configuration (as in, add another your_site.conf in /etc/apache/sites-available, and link to it from sites-enabled) repeating the entire VirtualHost and everything inside it, however, use a different listen port, or the same port, and add the ServerName directive to specify the unique DNS name. Likewise, with NGINX, except you here you repeat the full server block in another configuration, and changing the listen and/or server_name bits. Oh yeah - you'll probably be changing the folder location of the Drupal installation in there too, that should definitely help reduce some confusion.

Phew - we should have the web server out of the way now!

Part 2: BASH-ing the `php` command

Next up: PHP on your command line. Here, I'm referring to what you get when you type php on your command line once logged in (via ssh) as the user that manages the web site. In this section, I'm assuming that per best-practices, there are different users for each site. This method does not help much if you have but one user...though I guess it can help if they're both using the same non-default version of php, in contrast to, say, other users on the server.

Which PHP is which?

When a server has multiple versions of PHP, only one of them at a given time will ever live at the path /usr/bin/php. Ordinarily, this is what you get when you type just php at the command line. This also is what you'll get whenever you run a file with a shebang of #!/usr/bin/env php, meaning if you run drush (or wp-cli, for our Wordpress friends), you'll get whatever PHP is found there as well. You can run which php if you'd like to see where php is found.

At this point, definitely check php --version. If you are getting the version you want, you're are done with this article! Well, maybe not - you just want to switch to the account where you do require a different version of PHP than this gives you.

So, php --version gives one version, but there should be multiple PHP executables on the system now, right? (You should have installed multiple, or else have multiple versions available at this point). So where are those? These can be directly executed by running them using their versions in their name. For example php8.1 or php7.4. So, what is happening here that we just get "one proper php"? Well, a couple things.

Environmental conditions...

What, the smog? No...well, yes, that's a problem, but some good things happened in 2022. In this instance, our first issue comes from an environment variable. In particular: the venerable PATH, which contains a list of locations that the shell will use to look for a given executable as you enter a command name (or, again, as specified by a shebang). The PATH variable is a colon-delimited list, typically looking about like this: /usr/local/bin:/usr/bin:/bin:. The shell simply looks for the first occurrence of your command as it peeks in each directory in sequence (left-to-right). Is there a /usr/local/bin/php? That's what you'll get. If not, how about /usr/bin/php? And so on, until it finds one, or else you've mistyped pph and end up with command not found instead. You can see your path with echo $PATH (or try which agaric. I'm guessing you won't have an agaric program, this will tell you where it looked for it when it fail to find one).

What's the 'alternative': links all the way down

The second part of this equation is what is going on with the /usr/bin/php that was found. This alleged "The PHP" is actually a soft link to the current system-level default version of PHP that's installed. You can see how this situation is resolved with a command such as readlink -f /usr/bin/php. This command basically says "read the symbolic link (recursively, due to the -f, try it again without the -f!) and show what it's [ultimately] pointing to". This link (and those it links to) come from an "alternatives" system used by Debian-like systems that connects such things as the canonical name of an executable to a specific installed version. You can learn more about how how this is set up (for PHP, anyway) from...you guessed it: Ondřej's FAQ.

So...the fix then?

Now, where we have multiple versions of PHP installed, it's generally impractical to change all the shebang lines to something else, but that is technically one way to do things. We're also assuming you want to use multiple versions simultaneously here - so updating via the alternatives system isn't a great option either - if you can even do that. There is a simple method to make this work, even as an unprivileged user: make your own link called php and make sure the shell can find it in the PATH.

What we will do, is create our own ~/bin folder, and make our link there. Then, we just make sure that ~/bin is in our path, and comes before other locations that have a php file. There's no shortage of places for customizing the PATH (and bash, generally), and quite frankly, since I'm not positive what the canonical location is, I'll happily follow the Debian manual which says ~/.bashrc. The particular file you'll want to use can be influenced by the type of shell you request (a login vs non-login, and interactive vs non-interactive). In the manual, part of their example looks like this:


# set PATH so it includes user's private bin if it exists
if [ -d ~/bin ] ; then
  PATH="~/bin${PATH+:$PATH}"
fi
export PATH

Curious what that odd-looking ${PATH+:$PATH} syntax is about? Most people just refer to PATH as $PATH when they want it, like this PATH=~/bin:$PATH, right? Well, yes, and that will probably work just fine, but that weird reference does have some smarts. These are something called parameter expansions. Note that in proper bash parlance, the thing we've been calling a variable this whole time is referred to as a parameter. Go figure...that certainly didn't help me find this reference documentation. If you are interested in shell programming (which I clearly think everyone is, or should be) these can be very helpful to know. You'll bump into these when performing various checks and substitutions on varia-er, parameters. Check out the bash documentation to figure this one out.

OK, this looks good! Let's go ahead and add that to the ~/.bashrc file in your ssh user's home folder. Now, you either need to source the updated file (with . ~/.bashrc), or just reconnect. Sourcing (abbreviated with that previous ., otherwise spelled out as source ~/.bashrc) essentially executes the referenced file in a way that can it can modify the current shell's context. If you were to just run ~/.bashrc without sourcing it, it happily sets up an environment, but all that gets wiped out when the script ends.

Now, let's get moving with this plan again. Make a bin folder in our ssh user's home folder: mkdir ~/bin. Finally, link the php version you want in there. Here, I'll do php8.1: ln -s /usr/bin/php8.1 ~/bin/php. Voila! Now when you run php --version, you'll get PHP 8.1!

Another diversion, really?

For the impatient: reconnect and skip to the paragraph. For the curious (or when you have time to come back): it turns out our shell has a bit of memory we never expected it to. If you've typed php sometime earlier in your session, and bash last found that it was the one in /usr/bin/php that came up first, it's now remembered that so it doesn't have to find it again. While you can just re-login - again! - you might also wan to take the reins of your system and try typing hash -d php (see help hash to learn what that does - the help command covers shell built-in functionality, like hash). At last, php really works the way we wanted! No more odd little shell corners hiding dusty references on us.

OMG, that works?

Finally...despite my droning on, we're making progress! At this point, when you call upon your drush status, it should actually run without errors (some things under php7.x just don't work now - as expected) and show that it's using the correct php version.

Part 3: Drush does speak SSH, but not YOUR SSH.

The Drush boffins graced us with doodads called site aliases that allow us to readily send commands to various sites we control. If you don't know about those, you'll have to read up on them first. The prolific Moshe Weitzman (along with some 310 contributors, and counting) didn't give us these because they were getting bored after 20 years of Drupal; They're pretty essential to working with Drupal effectively.

Assuming you have a grasp of aliases under your belt, let's try a Drush command from our local environment to the remote system: drush @test status. OK - something is wrong here. My Drush just said I'm still using php7.4 and not my beloved 8.1...again! Does Drush have a memory too? No, it just doesn't care how you want your ssh sessions to work; those are left to you and Drush does it's own thing. The ssh connection that Drush opens has it's own context set-up, unlike the one we have when we've ssh'd in interactively. Thankfully, there's a quick fix for this - they key Drush needs to know is how to set the PATH up so it also gets our targeted PHP version. Let's add our modified PATH to the site aliases configuration, so Drush also knows what the cool kids are doing.

live:
  host: example.com
  paths:
    drush-script: /home/example-live/vendor/bin/drush
  env-vars:
    PATH: /home/example-live/bin:/usr/bin:/bin
  root: /home/example-live/web
  uri: 'https://example.com/'
  user: example-live

Note the specified PATH of /home/example-live/bin:/bin:/usr/bin places the bin directory in home at the beginning again. At last, when we run drush @test status, it's telling us the PHP it uses is the one that we use. We can share, Drush. We made it!

Thanks for stopping by!

That wraps it up for this one. Hopefully you now feel a little more confident you have some tools helping you master your environment. The version of PHP you need is now at your command. Now you can go get busy with your composer updates and other TODO's. By the way, if all this configuration headache is just not for you - check out our hosted Drutopia platform. We run an instance of this distribution, and provide all these niceties as part of our hosting setup, so reach out if interested. Either way, thanks for coming by!

Oh yeah!

I'm back! Because I knew this blog post wasn't quite long enough already...certainly not because it occurred to me that I left off another alternative I've used in a pinch.

The one-off

If you want to run a command using a different version of php, rather than rely on the shebang, you can always just call the desired script as an argument to php itself. When you run, e.g. drush status, your shell sees the #! on the first line, and uses that as the default interpreter for the script. In essence, your command is translated into /usr/bin/env php drush status.

Rather than accept this default, you can specify the specific version on your command line, such as php8.1 drush status. In this case the #! is just ignored. Unfortunately, this method can only get you so far - particularly with drush. Drush often figures out what your command requires and in turn will spawn other php scripts to do the dirty work. These spawned scripts are unaware of the interpreter that drush itself was started with.

Comments

2023 February 21
Wilbur Ince

Permalink

Thanks for this in-depth…

Thanks for this in-depth consideration! This can get really confusing when bash php does not match the website php! Great insight!

Add new comment

The content of this field is kept private and will not be shown publicly.

Markdown

The comment language code.
CAPTCHA Please help us focus on people and not spambots by answering this question.