interfacelab

Avatar

NGINX + PHP-FPM + APC = Awesome

The following guide will walk you through setting up possibly the fastest way to serve PHP known to man. If there is a faster way, I’ve not yet found it climbing through zillions of blog posts out there on the subject. In this article, we’ll be installing nginx http server, PHP with the PHP-FPM patches, as well as APC. The end result? Pure awesome.

Some Background

For the last 2+ years, we’ve been running Apache with mod_php at Massify. And, for the most part, it’s been painless and hassle free. But as traffic increases, we’ve noticed Apache struggling to keep up. It chews up memory like crazy and don’t even get me started about CPU. Add in the fact that we are currently moving the site from co-located metal to hosting on the cloud, we’ve needed to find a faster way to serve up these pages on machines that are a quarter of the spec we had with the metal.

It’s a pretty well know fact by now that Nginx (pronounced engine-x, though I call it n-jinx) in typical scenarios outpaces Apache on all kinds of fronts: i/o, cpu, memory, reqs/sec. Feel free to google around for more comparitive information. I can tell you from my own informal load testing on both setups of Massify that I’ve seen a pronounced difference between the two, specifically in the configuration I’ll be writing about in this post. I won’t be posting numbers, because my testing wasn’t scientific and it’s not really the focus of this article, but I am more than confident that we’ll be getting a level of performance a few steps above Apache. (Please don’t write me telling me I can recompile and reconfigure Apache to approach Nginx’s performance. I’m certain you are correct, but I don’t really care at this point.)

PHP-FPM?

The typical nginx configuration involves using spawn-fcgi from the LightTPD project to get nginx serving up PHP. There are a few issues with spawn-fcgi that are covered on more in-depth posts across the interwebs, so I won’t rehash them here. Suffice to say, it can lead to major headaches, so we want to do what we can to avoid those headaches.

Enter PHP-FPM, which stands for PHP FastCGI Process Manager. It is actually a set of patches for the PHP source that bake in FastCGI process management into your PHP binaries.

Note: Even if you are sticking with Apache, there are a variety of reasons to skip mod_php and go straight for PHP via FastCGI. For starters, with mod_php, each request that Apache handles loads PHP – and all of it’s libraries. That’s an epic shit ton of overhead. With FastCGI, PHP acts more akin to an application server. PHP-FPM, as well as spawn-fcgi, manage this; loading and killing PHP instances as needed. This has a lot of benefits, not the least of which is reduced memory overhead.

Let’s Get Rocking

We’re using a fresh install of Ubuntu 8.10 Intrepid. With a little tooling, this can work equally well on other linux distros. I’ve deployed this on CentOS, with minor changes, just fine.

First things first, we’re going to have to install all the dependencies for building everything:

sudo apt-get install make bison flex gcc patch autoconf subversion locate
sudo apt-get install libxml2-dev libbz2-dev libpcre3-dev libssl-dev zlib1g-dev libmcrypt-dev libmhash-dev libmhash2 libcurl4-openssl-dev libpq-dev libpq5 libsyck0-dev

Now that we have everything we need, now it’s time to …

Compile PHP

We’re going to download the source for PHP 5.2.8, as well as the matching patches for PHP-FPM. We’ll then apply the patches and get compling.

cd /usr/local/src/
sudo wget http://us.php.net/get/php-5.2.8.tar.gz/from/this/mirror
sudo tar zvxf php-5.2.8.tar.gz
sudo wget http://php-fpm.anight.org/downloads/head/php-5.2.8-fpm-0.5.10.diff.gz
sudo gzip -cd php-5.2.8-fpm-0.5.10.diff.gz | sudo patch -d php-5.2.8 -p1
cd php-5.2.8
sudo ./configure --enable-fastcgi --enable-fpm --with-mcrypt --with-zlib --enable-mbstring --disable-pdo --with-pgsql --with-curl --disable-debug --enable-pic --disable-rpath --enable-inline-optimization --with-bz2 --with-xml --with-zlib --enable-sockets --enable-sysvsem --enable-sysvshm --enable-pcntl --enable-mbregex --with-mhash --enable-xslt --enable-memcache --enable-zip --with-pcre-regex
sudo  make all install
sudo  strip /usr/local/bin/php-cgi

The above should have been completely auto-pilot. If you run into any errors, more than likely you don’t have some of the dependencies installed. Note: Make sure you enable and disable whatever PHP configuration options you need for your particular setup. The above are what we use at Massify, more or less. (We actually apply a couple of other custom patches, but that’s another post for another day).

While we are at it, let’s install some of the modules we’re going to need via PECL:

sudo pecl install memcache
sudo pecl install apc
sudo pecl install syck-beta

Now, when you build the APC extension, make sure you turn off the option to compile for apache. It will prompt you to make this change.

Let’s copy the stock php.ini:

sudo cp /usr/local/src/php-5.2.8/php.ini-recommended /usr/local/lib/php.ini

Finally, let’s setup symbolic links to make things easier to find:

sudo mkdir /etc/php/
sudo ln -s /usr/local/lib/php.ini /etc/php/php.ini
sudo ln -s /usr/local/etc/php-fpm.conf /etc/php/php-fpm.conf

That’s it for compiling PHP. The only thing we have left to do is change some setting in the php-fpm conf. Open up /etc/php/php-fpm.conf in your editor of choice. Do some searching and change the users who own the processes to www-data. The file is too big to post in here, but the values we want to change are on lines 51, 52, 63 and 66. Should look something like:

<value name="owner">www-data</value>
<value name="group">www-data</value>
<value name="user">www-data</value>
<value name="group">www-data</value>

Done!

NGINX

This is just as easy as compiling PHP was:

cd ..
sudo wget http://sysoev.ru/nginx/nginx-0.6.35.tar.gz
sudo tar zxvf nginx-0.6.35.tar.gz
sudo rm -f nginx-0.6.35.tar.gz
cd nginx-0.6.35
sudo ./configure --sbin-path=/usr/local/sbin --with-http_ssl_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_stub_status_module
sudo make && sudo make install

Let’s set up some links:

sudo ln -s /usr/local/nginx/conf /etc/nginx

Now the next step is optional, but this is what I’ve been using so far. Let’s open up /etc/nginx/nginx.conf in our editor and go to town. Salt and pepper to taste:

user  www-data;
worker_processes  6;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  10 10;

    gzip  on;
    gzip_comp_level 1;
    gzip_proxied any;
    gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        log_format main '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status  $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx_access.log  main;

        error_log  /var/log/nginx_error.log debug;

        include /usr/local/nginx/sites-enabled/*;
}

We’re also going to have to set up some FastCGI parameters so that PHP doesn’t choke and you’ll avoid random 503 errors from nginx. Open up /etc/nginx/fastcgi_params and add the following:

fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;

Finally, let’s create a SystemV style init script and store it in /etc/init.d/nginx. This script was lifted from here.

#! /bin/sh

### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/sbin/nginx
NAME=nginx
DESC=nginx

test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
        . /etc/default/nginx
fi

set -e

case "$1" in
  start)
        echo -n "Starting $DESC: "
        start-stop-daemon --start --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
                --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  stop)
        echo -n "Stopping $DESC: "
        start-stop-daemon --stop --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
                --exec $DAEMON
        echo "$NAME."
        ;;
  restart|force-reload)
        echo -n "Restarting $DESC: "
        start-stop-daemon --stop --quiet --pidfile \
                /usr/local/nginx/logs/$NAME.pid --exec $DAEMON
        sleep 1
        start-stop-daemon --start --quiet --pidfile \
                /usr/local/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  reload)
      echo -n "Reloading $DESC configuration: "
      start-stop-daemon --stop --signal HUP --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
          --exec $DAEMON
      echo "$NAME."
      ;;
  *)
        N=/etc/init.d/$NAME
        echo "Usage: $N {start|stop|restart|force-reload}" >&2
        exit 1
        ;;
esac

exit 0

Don’t forget to set executable permissions on it!

Set Up Your Site

This part you’ll have to tool yourself, but here’s a rough guide.

First we’ll have to create a directory to store our site(s) conf files:

sudo mkdir /usr/local/nginx/sites-enabled
sudo ln -s /usr/local/nginx/sites-enabled /etc/sites

And now let’s add a conf file for our default site at /etc/sites/default.conf. The contents:

server {
        listen *:80;

        location / {
                root   /var/www/default/pub;
                index index.php;

                # if file exists return it right away
                if (-f $request_filename) {
                        break;
                }

                # otherwise rewrite the fucker
                if (!-e $request_filename) {
                        rewrite ^(.+)$ /index.php$1 last;
                        break;
                }

        }

        # if the request starts with our frontcontroller, pass it on to fastcgi
        location ~ ^/index.php
        {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_param SCRIPT_FILENAME /var/www/default/pub$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_script_name;
                include /usr/local/nginx/conf/fastcgi_params;
        }
}

The conf file above is for front controller style sites, which include wordpress, cake, etc. It also takes care of serving up static content without going through FastCGI. Note: You will need to change /var/www/default to wherever the files for your site reside.

Start It Up

Now all we have to do to get it rocking:

sudo php-fm start
sudo /etc/init.d/nginx start

You should be able to test your site out and see it working. Any questions, feel free to ask.

Reference

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]
1 Star2 Stars3 Stars4 Stars5 Stars (30 votes, average: 4.67 out of 5)
Loading ... Loading ...
  • I changed from apache into lighttpd. It's very light weight and faster.
    I'll try nginx.

    Thanks for great post.
  • zeomni
    This method is the most powerfull but you have to use very fast and tested php code !

    This method use few workers: One Slow script = One worker not available during the slow execution

    You must avoid in your php script :
    - Slow Sql Query
    - Image Manipulation
    - File Download using php
    ... All other slow tasks.

    Else your website will be in deadlock.
  • nowthatsamatt
    This is an amazing reference, thanks for sharing in such a well formed way.

    Small typo: "sudo php-fm start" should be "sudo php-fpm start".
  • Kettle
    Hey there -- this is great, but can you let me know which repos you're using? Whenever I try compiling PHP, I get all kinds of insufficient version warnings... Using same distro from same provider, so not sure what else I could be doing wrong...
  • Vladimir
    Strange thing - the php-code is not getting executed by php; even if i try it directly in the shell:

    ---------
    h100:/usr/local/nginx/sites-enabled# /usr/local/bin/php -f /var/www/default/pub/index.php
    <?

    echo "Hello";
    phpinfo();

    ?>
    h100:/usr/local/nginx/sites-enabled#
    ---------

    What can be the cause?
  • Anon
    you probably have shorttags disabled... try <?php echo 'test'; ?> instead
  • hvs69
    I deleted /usr/local/bin/php and /usr/local/bin/php-cgi by accident. As a result pecl and php-fpm commands give me error saying /usr/local/bin/php not found.

    Can someone please tell me how to rebuild these files? I have been searching for this on google but cannot find an answer.

    Thanks.
  • nnyan
    What repo do I need to enable to get the following files (in Centos 5)?

    No package libbz2-devel available.
    No package libpcre3-devel available.
    No package libssl-devel available.
    No package zlib1g-devel available.
    No package libmhash2 available.
    No package libcurl4-openssl-devel available.
    No package libpq-devel available.
    No package libpq5 available.
    No package libsyck0-devel available.

    Unable to find them anywhere (added RPMForge and EPEL to no avail). Thank you
  • joelverks
    use netselect to update your /etc/apt/sources.list file (worked for debian)

    apt-get install netselect
    apt-get install netselect-apt
    sudo netselect-apt -n -s stable

    this will backup your old sources.list file and create a new one with the fastest current mirrors
  • vizualbox
    Looking good. Is there any particular reason why you didn't install nginx with just *sudo apt-get install nginx*?
  • Site works fine here, as do plenty of other WordPress sites.
  • stame
    I follow the tutorial on Ununtu 8.04, and php configuration says this at the end:

    Notice: Following unknown configure options were used:

    --enable-fpm
    --enable-pic
    --with-xml
    --enable-xslt
    --enable-memcache

    Check './configure --help' for available options

    Finally, when I complete everything, php files are not processed, the browser just wants to download them. Anyone has any idea?

    Great tutorial, by the way.
  • stame
    So... it is working, but these unknown configuration options disturb me. Why are they unknown, and will I need them? I will try to use pfp-fpm with Drupal.
  • stame
    I used php-5.2.11 and php-fpm-0.6~5.2.11
  • stame
    Solved. Forgot that we are only processing index.php and not *.php:

    location ~ ^/index.php
  • salexiev
    what about with-gd?
  • Ivo
    APC!? Screw that, give memcached a try!!!
  • vonchuckles
    @Ivo: APC and memcached are tools for different purposes.

    APC contains a memory-backed data cache feature (which is what memcached is), however that is not its primary purpose-- its just an add-on. It's real purpose is to cache the interpreted versions of the scripts used in the site, in memory. This can increase execution speed of PHP scripts for most sites by 200% to 1000% or more. Memcached cannot do this. If you're running a PHP-heavy site and not using APC, you're wasting insane amounts of CPU cycles. There may be some obscure exceptions, but so far I have yet to see an example where APC doesn't provide a drastic speedup.

    You'll notice in the instructions however that he does install the memcache PECL extension as well, meaning that he already does (probably) use memcache for data storage, but uses APC for its (intended) opcode caching purpose.
  • Hi, first of all many thanks for this guid. I have a similar setup mostly thanks to this guide.
    One problem I am still having after a while, though, is that all the php-cgi processes die after a while the server has been in idle (I am working on a blog with no traffic yet other than mine) and no new ones are automatically started as soon as a new request is made through nginx. The result is that nginx will return a nice "502/bad gateway" until
    I manually run "php-fpm start" :-(

    Does anybody know a way to make this stuff automatic?
    Thanks in advance.

    ps: you might want to add an init.d script to your guide, to automatically start php-fpm at boot time.
  • Hey great tute but i am getting no input file in my browser when i browse to my site with an index.php and a phpinfo call in that file. Is there simple solution for this?
  • luka
    Can be latest version of php be used instead this version in this tutorial?
  • Alex
    One article how to install nginx you can find on the http://www.linuxspace.org/archives/1576

    I used it for my site, cuz I use ubuntu server
  • ahto111
    Have you tried nginx-ey-balancer, could it give a boost to this configuration?
  • iflyhigh
    No, I didn't even know it existed until you pointed it out. Will check it out, thanks for the heads up!
  • me
    I see you are using nginx/0.7.58 for this blog. But... your blog run slowly, like all others wordpress.
    So... with the same results, you can use apache.
    nginx and/or lighttpd are great for MMORPG or really high traffic websites
  • iflyhigh
    This site is actually run on a crappy $10 a month hosting joint.

    Check out http://massify.com/ which is what the article is based on.
  • Site works fine here, as do plenty of other WordPress sites.
  • Steven Vo
    I've encounter duplicated value of PHP_SELF for example:
    this php code
    print $_SERVER['PHP_SELF'];
    willl produce
    test.php/test.php


    To fix, comment off the line 26 in your file /etc/sites/default.conf:

    # if the request starts with our frontcontroller, pass it on to fastcgi
    location ~ ^/index.php
    {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME /var/www/default/pub$fastcgi_script_name;
    # fastcgi_param PATH_INFO $fastcgi_script_name;
    include /usr/local/nginx/conf/fastcgi_params;
    }
  • Steven Vo
    and should add in --with-mysql during the configure step of php installation as well.
    I encounter MySQL is not supported error now :(
  • I found that you need to install the source (dev?) MySQL client library for --with-mysql to work.
    # apt-get install libmysqlclient15-dev
  • Aco
    thanks so much for that... this drove me insane... =)
  • Steven Vo
    1 typo in "Start It Up" part:

    1. sudo php-fpm start
  • so I'm not at all experienced with this kind of stuff, but I was wondering if anyone has managed to install concrete5 cms using these settings?
    I managed to get to the point where the installer shows green checks across the board, but whe I try to istall it just halts half way and shows a blank page with a half full db…? managed to get wordpress going though.
    concrete5: http://www.concrete5.org/
    my site: http://67.23.25.191/
    php info: http://67.23.25.191/index.html

    and btw, what if I have a php file that isn't called index.php, how do I load it? like a simple script that resides either in the same folder as index.php or a subfolder … ?
  • Ben
    Unless I missed something I had to change the extensions directory to :
    "/usr/local/lib/php/extensions/no-debug-non-zts-20060613/"
    in php.ini to get apc working.
  • Charles
    One more thing to speed up PHP

    ./configure --with-zend-vm=GOTO
  • I'm using your settings, but I keep getting "No input file specified."!!! Aaaaaahhhhh!!!!
  • iflyhigh
    Give me a few hours, I have the solution for that particular problem.
  • hey did you manage to find the solution to this? I am getting the same issue. Love the tute btw
  • Phil
    I had this problem a few times and it was a path problem, make sure your site path in sites-enabled/default.conf is correct
  • Alexander
    Use this to fix "No input file specified":
    location ~ .php$ {
    fastcgi_param SCRIPT_FILENAME /path/to/root/$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    include /etc/nginx/fastcgi_params;
    }
    And restart nginx.
  • Michał Bialik
    You have a small mistake in last paragraph - instead of : "sudo php-fm" start should be "sudo php-fpm start" - by the way thank you very much for this guide - it saved me a lot of time.
  • Mark
    Does the fact that the PHP-FPM author has not updated the package since 13 December and that it still does not support PHP 5.2.9, worry you at all? (he's also been awol from their mailing list from what I can tell too). Yes, it's free and open source, so he's not obliged to maintain it of course. It's a promising piece of software, but it's a patch for PHP and is therefore tightly coupled with the PHP version (unlike Spawn-FCGI). It's one of the main things holding us back from using it.
  • nick
    I think the intention is to bake FPM into PHP 6
  • php-fpm will be in PHP 5.3.3
  • Phil
    Based on php-fpm google group, PHP-FPM will probably not be in 5.3.3 but 5.4. More info at http://bit.ly/bNTCdQ and http://bit.ly/ca2uw6
  • Yeah, and the uncertainty expressed in the first post you link to is fully understandable. My take: 90% chance it goes into 5.3.3. And fwiw, it's in trunk now which works perfectly well with 5.3.x, in case anyone feels the need. However, running buildconf is needed in that case (copied over dir).
  • Phil
    PHP-FPM is now in the 5.3 branch! http://bit.ly/d4BI9m
  • While I am currently deploying EXACTLY the config shown in the demo, I would like to point out that the problems it solves are NOT exactly what you might expect.
    Apache has 2 major problems when serving PHP.
    1: Holy memory usage batman! Apache forks a new process for each PHP script running, and can easily eat a HUGE amount of ram
    2: Slow clients will cause those processes to be held up, using all that ram, while the process serves the data to the client.

    By placing Nginx in front of PHP-FCGI, you are causing the page to render, then disconnect from the PHP process while Nginx serves the actual data. This means you can have fewer PHP processes, and in turn they use less ram. Tuning your fcgi buffers settings on Nginx can be important here, make sure it is big enough for most of your pages to cache in ram.

    An equally good solution is to place Nginx with a lot of big proxy buffers in front of an Apache server running a smaller # of maxclients. Also be sure to configure Nginx to handle static files directly. This is the best of both worlds, and we have seen much higher page throughputs with this config.
  • Anuraag
    Ya, I have nginx installed in front of apache, with nginx handling static files, and taking all the network overhead load off apache. Performance is markedly better, although I am not sure it will match up to this method.

    Of course, we have a lot of .htaccess magickery going on, and I am again uncertain if PHP-FPM would support that.
  • James Moss
    Do you have any numbers to show how much faster this method is compared to a normal apache/mod_php setup?
  • Thanks for the post. I am in the process of making a similar transition and was considering Lighttpd + PHP + Xcache. Did you look into that configuration? If so do you have any thoughts about it?
  • Owen
    the section about the init script seems to be a cut and paste error... the contents shown are actually the default site config.
  • Thanks for the heads up!

    Fixed now.
  • jc00ke
    I had looked into using the Lighty spawner for a project but I like this method much better.
blog comments powered by Disqus