Drupal 8 on nginx HHVM and PHP5-FPM fallback.

It is, without a doubt, an exciting time for everyone working with Drupal and PHP in general - a great way to round of 2015. We’ve seen two major releases of our favourite open source projects, Drupal 8 and PHP 7. Whilst the performance boost of PHP7 over PHP5.6 is beyond anecdotal - it is proven - the performance of Drupal 8 on PHP7 vs. PHP5.6 isn’t as great when compared to other open source projects (see Kinsta.com benchmarking).

What is clear from all benchmarking to date is that Facebook’s HHVM is the technology to beat - Drupal 8 in particular works great! That being said, there are some “minor incompatibilities” that the HHVM team is aware of, which means it is best having a backup solution for when things go wrong. Since Drupal 8 is tried and tested on PHP5 and the performance of PHP7 on Drupal isn’t substantial we shall fall back onto PHP5-FPM.

After reading Bjørn Johansen’s article on HHVM with PHP5-FPM fallback I felt the need to attempt a Drupal 8 specific setup.

Let’s begin.

Provision

Grab yourself a fresh VM - DigitalOcean and Amazon Web Services are pretty reasonable, start off with a small droplet/instance so that you aren’t incurring a huge cost. Spin up an Ubuntu 14.04 server. Make sure all the packages are all up to date.

Installation

You will need to add HHVM to your package manager, luckily in Ubuntu this is easy. First install software-properties-common and add the HHVM repository:

sudo apt-get install -y software-properties-common
sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x5a16e7281be7a449
sudo add-apt-repository -y "deb http://dl.hhvm.com/ubuntu $(lsb_release -sc) main"
sudo apt-get update

Now it is time to install our stack. Typically I would refer to the LEMP statck (Linux Nginx MySQL PHP) however this shoudl perhaps be referred to as a LEMPH (Linux Nginx MySQL PHP HHVM) stack. Answer all the basic questions presented from Postfix and MySQL as required.

sudo apt-get install -y nginx php5-fpm php5-gd php5-imagick php5-mcrypt php5-curl php5-json php5-mongo php5-mysql mysql-server hhvm postfix

Right, after setting this all up, we need to get configuring. We’re going to need a database, here I am being extremely lazy and calling it “drupal”

mysql -u root -p -e "CREATE DATABASE drupal;"

Now, a quick HHVM bugfix. To avoid a lot of error messages being generated when we run Drupal 8 we need to set the timezone for HHVM to use. There is a one-liner for this, I’m using UTC, use whatever you want:

sudo bash -c "echo 'date.timezone = \"UTC\"' >> /etc/hhvm/php.ini"

We also need to get HHVM to start up when the server starts.

sudo update-rc.d hhvm defaults

Next, lets get nginx sorted. Here is my basic vhost configuration for nginx/HHVM with PHP5-FPM failover.

server {
    listen 80;
    listen [::]:80;
    client_max_body_size 20M;

    root /var/www/example.com;
    index index.php index.html index.htm;
    server_name example.com;

    location ~ \..*/.*\.php$ { return 403; }

    location / {
        try_files $uri @rewrite;
    }

    location @rewrite {
        rewrite ^ /index.php;
    }

    location ~ \.(hh|php)$|^/update.php {
        fastcgi_intercept_errors on;
        error_page 500 = @fallback;
        error_page 502 = @fallback;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;

        fastcgi_keep_conn on;
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_NAME $host;
        fastcgi_pass 127.0.0.1:9000;
    }

    location @fallback {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param SERVER_NAME $host;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
    }

    location ~ /\.ht { deny all; }
}

So what does it do? In a nutshell our site is hosted in root folder /var/www/example.com - we listen on port 80 (HTTP) for connections to example.com. First off, rewrite all requests to /index.php (Drupal 8) - this is how Drupal handles request URIs.

Now, if we hit a .php file (or .hh for specific HHVM stuff) we first pass this to HHVM running as FastCGI on port 9000. Notice the fastcgi_intercept_errors on;? This is what we use to see if HHVM was able to run the Drupal code. So how do we deal with this failing? We redirect it to PHP5-FPM running on a Unix Socket, for this we catch the HHVM errors and redirect to @fallback, our block for running PHP5-FPM using error_page 500 = @fallback; error_page 502 = @fallback;.

Time to test it out. Let’s restart all our services for good measure and get testing.

sudo service mysql restart
sudo service hhvm restart
sudo service php5-fpm restart
sudo service nginx restart
sudo service postfix restart

Then visit your web page. Install Drupal 8 and enjoy.

Drupal 8 HHVM

Testing

If you press F12 on the page and refresh the page whilst on the “Network” tab you should see whether or not the page has run in HHVM or PHP5-FPM based on the page headers. All being well you should see HHVM/3.10 in the header - if not something has probably been misconfigured.

To check if the fallback is working, stop the HHVM service and refresh the page.

HHVM Working

HHVM Working

HHVM Stopped / PHP5-FPM Fallback

PHP5-FPM Fallback

Sources