Getting Nginx + PHP 5.3 to work, properly!

Over the past few days, I moved a few Apache2 + PHP webapps (including this WordPress blog) from a shared host to a dedicated CentOS server. By default the server came with Apache2 (httpd) web server, but I wanted to use Nginx instead. Why Nginx? It’s *much* faster and scales better with lower resource usage.

Installing Nginx, PHP and MySQL wasn’t a problem and there were tonnes of resources on the internet to help me through. Where I did face a challenge was to get Nginx and PHP to play nicely.

Before I explain what went wrong, I will note the differences in the way Apache2 and Nginx handle PHP.

For Apache2, in many cases, PHP is configured as an internal module. Alternatively it can also be used in CGI mode. However, no matter how it’s configured, it is the web server’s responsibility to interface with PHP (which means having to spawn PHP instances as an when required).

In the case of Nginx, things are very different. Casually put, Nginx acts as a proxy server and will redirect requests to a PHP-CGI server instance running on your box. This means, of course, you are now responsible for spawning a PHP-CGI server instance and configuring Nginx to use it.

Not so bad, so I went ahead and did it. Here’s where the real problem comes in.

After some time, I found that the PHP-CGI instance automatically hung up and needed to be restarted. In the browser this showed up as 502 Bad Gateway errors. I Google’d it for a few hours and couldn’t find a definite solution. However few links did indicate that some PHP modules were the cause for the PHP-CGI crash. The best solution out there I could find was to setup a cron to re-up the PHP-CGI instance every N minutes.

But even if this cron interval is 1 minute, it still leaves a low but significant probability that a user sees the error, refreshes immediately, sees the error again and quits your website. Not acceptable.

My quick fix solution? Reduce this probability. How?

Setup multiple instances of PHP-CGI

    $ php-cgi -b 127.0.0.1:9000 &
    $ php-cgi -b 127.0.0.1:9001 &
    $ php-cgi -b 127.0.0.1:9002 &
    $ php-cgi -b 127.0.0.1:9003 &
    $ php-cgi -b 127.0.0.1:9004 &

Setup the cron to re-up the PHP-CGI instances if they’re down

    $ crontab
    */2  *  *  *  *  php-cgi -b 127.0.0.1:9000
    */2  *  *  *  *  php-cgi -b 127.0.0.1:9001
    */2  *  *  *  *  php-cgi -b 127.0.0.1:9002
    */2  *  *  *  *  php-cgi -b 127.0.0.1:9003
    */2  *  *  *  *  php-cgi -b 127.0.0.1:9004

Configure Nginx to choose from this pool

    http {
        ...
        upstream phpfcgi {
            server 127.0.0.1:9000;
            server 127.0.0.1:9001;
            server 127.0.0.1:9002;
            server 127.0.0.1:9003;
            server 127.0.0.1:9004;
        }
        ...
        server {
            server_name        {server name};
            root               {path to document root};

            location / {
                index          index.php;
            }

            location ~\.php$ {
                fastcgi_pass    phpfcgi;
                fastcgi_index   index.php;
                include         fastcgi_params;
                fastcgi_param   SCRIPT_FILENAME $document_root/$fastcgi_script_name;
            }
        }

    }

This reduces the probability of the user to repeatedly see the 502 Bad Gateway errors. This can be improved by having the cron trigger a script which checks if the php-cgi process has hung up before attempting to restart it.

But a real solution would be to figure out which PHP module *really* is causing the php-cgi to hang in the first place and fix this bug.

I’ve also stumbled across something called PHP-FPM but I don’t know enough about it to as yet. You might consider checking that out.

Hope this helps.