Running HHVM with fallback to PHP-FPM

HHVM can really speed up your PHP-based web site. Most reports are somewhere in the range of 2–4x faster. Unfortunately, HHVM isn’t very stable and will suddenly die, just of the blue, from time to another. Fortunately, if you’re running Nginx it’s really easy to set up PHP-FPM as a fallback.

Configure HHVM and PHP-FPM

First, make sure you have a web site that works with PHP-FPM, then replace it with HHVM and make sure it runs fine.

My PHP config looks like this:

location ~ \.php$ {

    try_files $uri =404;

    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;
}

And my HHVM config looks like this:

location ~ \.(hh|php)$ {

    try_files $uri =404;

    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;
}

Yes, they are pretty much alike, and for absolutely no obvious reason, I run HHVM over a port and PHP-FPM through a socket. There are religious wars on the Internet on what’s best. I take no stand.

The important thing is that you don’t run HHVM and PHP-FPM over the same port or socket, since they both have to run at the same time.

Make sure your website runs fine when either of these configs are active (not both at the same time).

Use both configs – with PHP-FPM as fallback

Next we’re going to merge the two configs, into one config where HHVM is the one that will primarily handle .php-files. We will intercept the errors from HHVM, and the PHP-FPM config will be used as a fallback “location” if the error code is 502 (“Bad Gateway”), which is what is returned when HHVM falls on its face.

location ~ \.(hh|php)$ {
    fastcgi_intercept_errors on;
    error_page 502 = @fallback;

    try_files $uri =404;

    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 {

    try_files $uri =404;

    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;

}

Test the fallback

Make sure HHVM, PHP-FPM and Nginx is running (remember to reload Nginx after changing the config), open a terminal window and execute (replace the URL with your own):

$ curl -I https://example.com

If you haven’t removed the header X-Powered-By, you should see this line in your output:

X-Powered-By: HHVM/3.4.0

Now stop HHVM on the server:

$ service hhvm stop

The above curl command should now give you something like:

X-Powered-By: PHP/5.5.9-1ubuntu4.5

So whenever HHVM goes AWOL, the request will be passed on to PHP-FPM who will serve as backup.

Now we know that PHP-FPM will function as a fallback if HHVM dies. To make sure that PHP-FPM will function as a fallback if HHVM returns a “502 Bad Gateway”, you can use a PHP script like this one:

Save it as fallback-test.php in your document root and test it with curl:

$ curl -i https://example.com/fallback.php

You should see the PHP-FPM X-Powered-By header and “PHP Fallback” as the body content.

Restart HHVM automatically

Now we have a mechanism in place that will fill in for HHVM. But since we have better things to do than check what service is serving our visitors, we’ll make sure HHVM is restarted automatically when it goes away.

ps-watcher is a tool that watches which processes are running and executes an action on defined circumstances. Let’s install it:

$ apt-get install ps-watcher

Edit /etc/ps-watcher.conf (you might have to create it) and add the following lines:

[hhvm]
occurs = none
action = service hhvm restart

Enable ps-watcher to start:

$ sed -i -e 's/# startup=1/startup=1/g' /etc/default/ps-watcher

By default ps-watcher will run every 5 minutes (or whatever is set in your /etc/default/ps-watcher – which on Ubuntu by default is set to 150 seconds). According to our config, the action service hhvm restart will be run if the regex hhvm cause no matches in the ps command output.

Start ps-watcher:

$ service ps-watcher start

If you now kill HHVM manually, you should see it back up before 150 seconds have passed by.

There, that’s it! Now you have a stable server with HHVM.

4 Comments

  1. Another option is to use the upstream directive.
    In this example, PHP FPM will be used when HHVM stops responding to requests.

    upstream php_servers {
    server 127.0.0.1:8000;
    server unix:/var/run/php5-fpm.sock backup;
    }

    location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_keep_conn on;
    fastcgi_pass php_servers;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    }

Comments are closed.