How to set up nginx with PHP on Ubuntu

In an environment where RAM is the major constraint, Apache might not be your best bet. Since I have moved all of my web projects over to an unmanaged VPS, I was looking for ways to either optimize or replace the LAMP stack with something less resource hungry. One of the ways I found to decrease the RAM requirement of my web services was to replace Apache with an event driven web server called nginx. There are quite a few resources on setting it up but not many discuss how to marry nginx to PHP (to run a WordPress blog for example). The best guide I found so far is this one. I built upon it to create the simplest nginx+FastCGI/PHP setup possible.

Step 1: Installation

$ sudo apt-get install nginx php5-cgi

Many of the nginx/PHP guides out there will tell you that you need to install spawn-fcgi, and most of them will have you compiling it from source. It turns out that php5-cgi package contains a FastCGI wrapper already, so the above two packages are all you need to get going.

Step 2: Startup script for FastCGI

We want to create a Startup script for FastCGI PHP processes to run on every boot up. Here is the script I use:

!/bin/bash
BIND_DIR=/var/run/php-fastcgi
BIND="$BIND_DIR/php.sock"
USER=www-data
PHP_FCGI_CHILDREN=8
PHP_FCGI_MAX_REQUESTS=1000

PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
RETVAL=0

start() {
    echo -n "Starting PHP FastCGI: "
    mkdir $BIND_DIR
    chown -R $USER $BIND_DIR
    start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
    RETVAL=$?
    echo "$PHP_CGI_NAME."
}
stop() {
    echo -n "Stopping PHP FastCGI: "
    killall -q -w -u $USER $PHP_CGI
    RETVAL=$?
    rm -rf $BIND_DIR
    echo "$PHP_CGI_NAME."
}

case "$1" in
    start)
        start
  ;;
    stop)
        stop
  ;;
    restart)
        stop
        start
  ;;
    *)
        echo "Usage: php-fastcgi {start|stop|restart}"
        exit 1
  ;;
esac
exit $RETVAL

Put the text above into /etc/init.d/fastcgi-php. Then run:

$ sudo chmod 755 /etc/init.d/fastcgi-php
$ sudo update-rc.d fastcgi-php defaults
$ sudo /etc/init.d/fastcgi-php start

Note the variables at the top of the script and adjust to fit your available RAM. The the php-cgi processes will get bigger after a while so allot at least 16-20MB for each. This script is slightly different than most I've found, in that it uses a UNIX socket instead of a TCP one for communication. UNIX sockets are faster, and you never have to worry about your firewall setup.

Step 3: Enable PHP processing

Create a new file /etc/nginx/fastcgi_php with this content:

# pass the PHP scripts to FastCGI server listening on UNIX socket
location ~ \.php$ {
    fastcgi_pass   unix:/var/run/php-fastcgi/php.sock;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include /etc/nginx/fastcgi_params;
}

Now define you virtual servers. Here is a sample config:

server {
    listen   80;
    server_name  example.com www.example.com;

    access_log  /var/log/nginx/example.com.access.log;

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

    error_page  404  /404.html;
    error_page   500 502 503 504  /50x.html;

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny  all;
    }
    # Enable PHP
    include /etc/nginx/fastcgi_php;
}

Notice the next to last line of the file. You can include this line in all of your server definitions to enable PHP processing through FastCGI.

Step 4: Enable the site:

Enable the site and reload the configs:

$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
$ sudo /etc/init.d/nginx reload

Optional tweaking

There are several other things you might want to consider once this setup is in place. First of all, nginx does not process htaccess files since those are Apache specific. For the most part this means that mod_rewrite rules you've had in place won't work. The good news is that nginx has its own URL rewriting engine, which is quite capable. The bad news is that you will have to translate your mod_rewrite rules to the syntax used by nginx.

WordPress compatibility

A "quick fix" exists for WordPress:

server {
    # Your server definition
    # ...

    location / {
        # this serves static files that exist without running other rewrite tests
        if (-f $request_filename) {
            expires 30d;
            break;
        }

        # this sends all non-existing file or directory requests to index.php
        if (!-e $request_filename) {
            rewrite ^(.+)$ /index.php?q=$1 last;
        }
    }

    # End of server definition
}

Note that if you are using the above rules and WP Super Cache, the cache will be used in only "half-on" mode, so you might want to at least consider translating the rewrite rules used by it to take full advantage of the caching.

Security considerations

Security of this setup can be increased if you define separate pools of PHP processes for each virtual server or even application, running as separate users. You can do so by running separate startup scripts. Just define each pool to use its own UNIX socket and instead of inlining the /etc/nginx/fastcgi_php file, write its contents with the customized socket name in each virtual server config.