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 (34 votes, average: 4.71 out of 5)
Loading ... Loading ...
  • Pingback: Nginx and Rails and PHP, Oh My! | geek#

  • 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

    Solved. Forgot that we are only processing index.php and not *.php:

    location ~ ^/index.php

  • 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

  • http://jk.scanmon.com/en Jacky

    Site works fine here, as do plenty of other WordPress sites.

  • Pingback: nginx+php+fpm+apc=awesome » ????

  • Pingback: Document, nginx+php-fpm+wordpress « CAPITOLEF/FECT

  • vizualbox

    Looking good. Is there any particular reason why you didn't install nginx with just *sudo apt-get install nginx*?

  • 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

  • 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.

  • 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

  • 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…

  • Pingback: Drupal LAMP Server Tuning (part 6)

  • Pingback: NGINX + PHP-FPM + APC = Awesome « Home page’s Alupov Artem

  • 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?

  • Pingback: ???? » Blog Archive » ubuntu(9.10)???nginx+php5.2+php-fpm+memcache+apc

  • 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.

  • Pingback: Easily setup LEMP on Linode - I'm Knight

  • Pingback: Nginx + PHP-FPM et WP-Super-Cache ? | AbriCoCotier.fr

  • Pingback: PHP as Apache mod and Quota (algo migrations) - cPanel Forums

  • Pingback: Consciousness » How to compile nginx / fcgi / PHP from scratch in CentOS 5 the CORRECT way

  • Pingback: lemp server[Linux nginx mysql php]+wordpress+port forwarding_dynamic dns{2nd spirula task} « Challenge

  • Anon

    you probably have shorttags disabled… try <?php echo 'test'; ?> instead

  • 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”.

  • http://blog.roshambo.org/ philipolson

    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

  • http://blog.roshambo.org/ philipolson

    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

  • 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.

  • http://www.thai-classified.com/ ???????????

    I changed from apache into lighttpd. It's very light weight and faster.
    I'll try nginx.

    Thanks for great post.

  • http://twitter.com/bakulrujak Andika Fajar K

    I wonder, how to inspect if APC is working fine ?

  • Phil

    I'm currently running PHP-FPM and APC and I haven't encounter any problems. Have you tried using the apc stats script?

  • http://www.thai-classified.com/ ???????????

    My life is better with nginx.

  • http://www.thai-classified.com/ ???????????

    My life is better with nginx.

  • igordubai

    cvcd

  • Anonymous

    Can you explain the following two directives:

    fastcgi_temp_file_write_size
    fastcgi_busy_buffers_size

    They seem to be undocumented and a Google search did not even bring up this blog entry. :)

    I also don’t understand why you trippled the backend connect, read and write timeouts from 60 to 180. Can you explain what lead to this decision?

  • http://pulse.yahoo.com/_URPMTCETIEQBRKQK23KR5FXRZU Eric D

    This is FANTASTIC!Given the release of 10.4 LTS and new releases of nginx, php-fpm and apc – would you consider updating the script? I’m sure I wouldn’t be the only eternally grateful reader. Thanks so much!

  • http://twitter.com/michaelkebriggs Tokyito 3

    Anyone know of a good hosting company for nginx. I fancy doing some tests – someone in the UK would be preferable. Thx

  • http://pulse.yahoo.com/_NMNQL2SPAC7ZTNFJNP3D2E5REA Chelline

    Please help my configuration does not work
    server{
    listen 80;
    server_name http://www.lowtraffic.net;
    access_log /var/log/nginx/lowtraffic.net.access_log;
    error_log /var/log/nginx/lowtraffic.net.error_log;

    root /home/lowtraffic/public_html/;

    location / {
    set $memcached_key $uri;
    memcached_pass 127.0.0.1:11211;
    default_type text/html;
    error_page 404 405 = @fallback;
    }

    location ~* .(gif|jpg|jpeg|png|css|js|ico)$ {
    expires 30d;
    access_log off;
    }

    location @fallback {
    rewrite ^ /index.php?q=$uri last;
    }

    location ~ .php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME /home/lowtraffic/public_html/$fastcgi_script_name;
    include fastcgi_params;
    }
    }

  • http://twitter.com/baigey baigey

    This is a great tutorial, and provides a lot of useful background information. However, if you wish to run nginx+php at this point, with Lucid LTS, there is also the following way, which will allow you to not have to compile everything manually (though that can be fun).

    Note: You should still read this whole article, as it will give you better understanding.

    http://constantshift.com/install-php-fpm-5-3-2-on-ubuntu-10-04-lucid-lynx/

  • http://www.facebook.com/people/Ivan-de-la-Jara/100000917500567 Ivan de la Jara

    The tutorial got old and fails in some commands…

  • http://pulse.yahoo.com/_XSNPNGVNKWB6CV6C6SUD4NWVVQ Karl

    You wouldn’t have to manually define your script file name or document root path like that if you placed the root directive at the server { } level instead of the location level, and should be ~ .php , not ~ /index.php

  • http://profiles.google.com/keshtiari P K

    This is FANTASTIC!!!

    Given the release of 10.04 LTS and new releases of nginx, php-fpm and apc – would you consider updating the script?
    Thanks so much!

  • Anonymous

    so ur saying apache can be as quick as nginx when compiled properly, dont care about the point and go ahead and recompile 3 or 4 huge things for nginx instead *hint* *hint*:D

  • http://profiles.google.com/anilsgulati Anil Gulati

    I didn’t use this method, I used a combination of ‘official’ pages from Ubuntu and Nginx documentation. But nowhere can I find any direction on how to get the HTML PHP script tags working inside an HTML page. I’m really rusty on this part of web server set up and am used to Apache mod_php set up. Doesn’t anyone use PHP like that anymore?

  • http://profiles.google.com/elitescripts2000 Matt Kukowski

    Gawd, nginx rox so hard. Apache2 was great, it did it’s job to defeat Microsoft take over of the internet, but along with that comes old age and cruft. nginx is just beautiful and modern. My next replacement will be of MySQL using DRIZZLE. nginx+php-fpm+drizzle and your talkin speed freak less memory less filling baby.

  • Anonymous

    All those people whose lives have been positively affected and touched by nginx can please stop commenting and send a donation to my paypal account. part of it will be sent to nginx dev community.

  • http://www.rare-earth-tech.com Rajeev J

    I really do not understand it when people make sweeping statements like 
    >suffice to say, it can lead to major headaches, so we want to do what we can to avoid those headaches. 
    I would really appreciate if you can give a concrete description of those headache cases as well. I can also make a statement like, Before 5.3.6 no guy would like to run php-fpm because the distros do not support it and from a security perspective it is just pesky to maintain packages on your own. Also, spawn scripts from lighty were a standard way to run PHP fastcgi with nginx and I never had issues with them. There are advantages and disadvantages of every approach and people should refrain from making motherhood statements and instead just give data.

  • http://www.facebook.com/profile.php?id=777208326 Raffaello Galli

    Great article.
    I tried to follow your hints to compile PHP (5.3.6) on a CentOS 5, but I am unable to find any
    php.ini-recommended file.Actually the only php.ini* file I see in the system is /etc/php.ini

     Raffaello