Getting Started with Nginx and PHP-FPM

by Mike Willbanks on July 12th, 2010

Nginx, a HTTP and reverse proxy server, known for its blazing speed in serving static files, including grand performance in terms of serving up FastCGI pages makes for a great coupling with the upcoming PHP-FPM sapi in PHP (It is currently in the 5.3 branch and previously was a patch) offer a great solution for finally getting rid of that old sloppy mod_php in Apache. Do you have the same issue where your apache instances have started to run too large? This might be the time to start to move forward.

A little background on why I have endeavored on this path. While running Apache at several jobs and institutions it simply became clear that under heavier traffic loads having all of the ridged custom components of Apache were starting to slow things down. To the point of large instances running sometimes up to 300MB in size. Having this size just to get a simple image or PHP page was just not necessary as well as proved to be a performance bottleneck.

While the first point of departure is to utilize a faster web server to proxy the existing one, it still does not stand to the point of keeping everything to a single task. This is where the FastCGI instances of PHP started to come in very useful as well as having something to monitor the processes aka PHP-FPM.

Updates

  • 2010/07/13: I have updated the Nginx configuration to put in place what Martin Fjordvald had mentioned in his comment. The fastcgi_params are now far quicker to put in! Thanks again!.

Overview

This posting assumes that you are not operating under a shared hosting account and that you have some dedicated hardware, a virtual machine or something in the cloud. Currently I have just switched from a dedicated server at The Planet to their new cloud offering which reduced my immediate costs substantially (149 to 99 per month for essentially the same thing but even better – my disk now operates off of a SAN. To you cloud junkies, you know disk speed / IO wait time is a pain in the a**. This simply mitigates these types of issues). Which has allowed me to take some time to move over to Nginx and PHP-FPM without having to have reconfigured my existing machine to what I wanted.

Further, you must be comfortable in running everything under a single user account, this is not something for a shared hosting supplier (while you can accomplish this, it is not why I am writing this post since the security of which, is just terrible). Simply put, if you have a shared hosting account switch to a VM (cloud hosting is a VM) or a dedicated server. This may not make some of you happy to hear but there are generally a large amount of issues with shared hosting – mostly related to security. But that is another rant…. Let’s get started.

PHP-FPM

PHP-FPM is a FastCGI process manager for PHP, say goodbye to the spawn-fcgi script from Lighttpd. The PHP-FPM has additional features that can make it appealing for any crowd. To name a few:

  • Emergency restart in case of accidental opcode cache destruction
  • Enhancements to FastCGI, such as fastcgi_finish_request() – a special function to finish request & flush all data while continuing to do something time-consuming
  • Ability to start workers with different uid/gid/chroot/environment and different php.ini (replaces safe_mode) – Covered in the PHP-FPM workers documentation.
  • Adaptive process spawning

PHP-FPM has been available for quite some time, however, it is new to the PHP 5.3 branch (to date, it has not been in a packaged release from PHP). There is a bit of configuration that you will need to do with PHP in order to get up and running with PHP-FPM. If you are afraid of compiling or not using your package managers builds, then you may want to stop reading now.

PHP 5.3 + PHP-FPM Installation

Installing PHP 5.3 and including PHP-FPM is simple. I tend to statically compile many of my extensions into PHP to make maintenance more simple with multiple machines. However, this is not a necessity. To get going follow these instructions (if you already have PHP and want to maintain some of your current configuration do a php -i from the command line and grab the configure statement – excluding apache related configuration (aka apxs). I’ve removed most of my configuration from this tutorial. You may find that you need additional librarys installed (just install them through your package manager).

#!/bin/bash
wget http://us2.php.net/get/php-5.3.2.tar.gz/from/www.php.net/mirror
tar -zxf php-5.3.2.tar.gz
cd php-5.3.2
svn export http://svn.php.net/repository/php/php-src/branches/PHP_5_3/sapi/fpm sapi/fpm
rm -rf autom4te.cache
rm -f configure
./buildconf --force
./configure \
--with-layout=GNU \
--with-libdir=lib64 \
--enable-fpm \
--with-gd \
--enable-mbstring \
--enable-pcntl \
--enable-soap \
--enable-sockets \
--enable-sqlite-utf8 \
--enable-zip \
--with-zlib \
--with-curl \
--with-pcre \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-gettext \
--with-mcrypt \
--with-mysql \
--with-mysqli \
--with-pdo-mysql \
--with-pdo-sqlite \
--with-tidy \
--with-pear \
--with-xml \
--disable-debug
make && make install

Add Your Init Script

Download the script from: http://svn.php.net/repository/php/php-src/branches/PHP_5_3/sapi/fpm/init.d.php-fpm.in

Now that you have the script, modify it to contain the correct paths. I am not going to do this here since if you’re running your own server or virtual machine I feel you should know to do such things :).

Update Your Configuration

The default may run fine for you, however, it is always best to take a peak at what users are running, your port numbers and ensuring that all of your items are setup the way you want them. Look for php-fpm.conf under /etc/php-fpm.conf or /usr/local/etc/php-fpm.conf. Then update this file to your liking. Once coompleted, start it up: /etc/init.d/php-fpm start – if you have issues, ensure that your variables are correct and that the configuration lines up with the init script (aka pid file for instance).

Nginx

Installation

Most of your distributions support nginx. You can install this through your package manager, however, I prefer to have the most recent versions (and running a redhat based distro that generally requires installing from source or creating a package – there is no .spec file so I took the easy way out) therefore, I decided to install from source which is a simple task but yet I customized it a bit to exclude a few things I did not need.

Steps that you will need to follow (to some degree):

  1. Download Nginx (we’ll use stable for now)
  2. Extract the Package
  3. Configure, Make and Make Install

Just as a note, I generally save my configuration in a shell script to allow for easy upgrading. You may want to do the same as it will allow you to quickly upgrade rather than attempting to figure out what you’re configure script was.

wget http://nginx.org/download/nginx-0.7.xx.tar.gz #not the real link, find current stable release on 7.x series)
tar -zxf nginx-0.7.xx.tar.gz
cd nginx-0.7.xx
./configure --with-http_ssl_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_stub_status_module
make && make install

So that was easy, or did you take the cheater way out and just install the package (which most are at 0.6)? Either way, this should still work for you.

Many tutorials (especially the one that I looked at beginning to convert to nginx simply rely on a single index.php – not that it is an issue but be wary about these tutorials since many of them exhibit issues related to security (one of which, is the first result in Google on a search for: nginx wordpress rewrite). I’ll say a little more once we get into the Site portion of the configuration.

The Main Configuration

The main configuration file is generally in /etc/nginx/nginx.conf or /usr/local/nginx/conf/nginx.conf. Here we want to setup a few of our variables to improve the server itself. There is a great guide from Slicehost on nginx defaults, you may want to get started there. Now my configuration is based quite a bit off of the Slicehost configuration as well as a few additional changes when we get more into the PHP section. For reference here is my configuration.

user  www-data;
worker_processes  4;
pid        logs/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    server_names_hash_bucket_size 128;
    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;
 
    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     off;
 
    keepalive_timeout  2;
 
    gzip  on;
    gzip_comp_level 2;
    gzip_proxied any;
    gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
 
    include /usr/local/nginx/sites-enabled/*;
}

One thing to note, is that due to some of my domain name sizes, I needed to increase the variable “server_names_hash_bucket_size” to 128 instead of the current default 32.

Setting up FastCGI Variables

These will be needed in order for your installation to work, as well as, setting up your environmental variables that get sent to PHP. You will notice most of these parameters.

; /etc/nginx/fastcgi_params
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param  PATH_INFO $fastcgi_script_name;
 
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
 
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
 
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

Now in that same file, you may want to also include some additional fastcgi configuration. These will likely need to be tuned for your environment and may take a little trial and error until you find the best fit.

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;

Setting up your First Site

Let’s configure a simple default site that just processes PHP scripts. No real hard trouble here! Create a new file in the sites-enabled directory called default.conf. We are going to have it parse our /var/www/default directory (if you do not have one, create one and add an index.php file of some sort in there). Here is a quick and simple configuration to get started:

server {
        listen 80 default;
 
        root   /var/www/default/public;
        index index.php;
 
        location ~ \.php$ {
                include /usr/local/nginx/conf/fastcgi_params;
                fastcgi_index index.php;
                if (-f $request_filename) {
                    fastcgi_pass 127.0.0.1:9000;
                }
        }
}

Note the location ~ \.php$, this tells us that for every PHP file to push it into fastcgi, now if you parse additional extensions through PHP, for the sake of everything, please add extensions to the location! Otherwise your precious PHP files can be downloaded. Yes, it is a serious thing!

What Not to Do

This configuration is evil and I actually found it on some tutorial but here it is, in it’s fully glory.

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

See how it only pushes things to index.php? Yes, this means that no other PHP files will be parsed. WTF mate? Yes, so please do not utilize a configuration such as this unless you know the repercussions.

Non-Default Sites aka Virtual Hosts

Virtual hosts are extremely easy in Nginx, all you need to define is the server name (the same goes with subdomains). You can basically copy out the default.conf into a specific site and add in a “server_name” variable to the “sever” section followed by removing “default” from the port. You can add in aliases by simply putting a space in between the servers. Here is an example (I also cache local files for a period of 30 days since they will likely not change):

server {
        listen 80;
        server_name digitalstruct.com www.digitalstruct.com;
 
        location / {
                root   /var/www/digitalstruct.com/public;
                index index.php;
 
        }
 
        # serve static files directly
        location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt)$ {
            access_log        off;
            expires           30d;
        }
 
        location ~ \.php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME /var/www/digitalstruct.com/public$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_script_name;
                include /usr/local/nginx/conf/fastcgi_params;
        }
}

That’s just about all you need for now with Nginx, if you run into issues the nginx wiki has quite a large amount of links to get you started in the right direction with rewriting those old nasty apache rewrite rules.

Conclusion

While this posting was not as detailed as I was hoping it to be, I hope that you can see some of the items that I was attempting to address, specifically the PHP portion in ensuring that you are covering yourself when it comes to ensuring that people are not downloading your configuration. Also if you are running multiple virtual hosts, it is a good idea to ensure that they are running under separate users.

From PHP

13 Comments
  1. Decent enough guide, there are a few things that most people never use but make the configuration file so much less likely to bugger up.

    First of all you should move your root directive out of location /. It makes no sense to have it there since you generally want it to apply every where. In fact, you barely ever need a location /.

    This also have the advantage that now $document_root is available in all your location blocks so you can change:

    fastcgi_param SCRIPT_FILENAME /var/www/digitalstruct.com/public$fastcgi_script_name;

    to

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    and throw it into your fastcgi config file, no confusion because of duplicated paths.

    You’re also running afoul of blindly passing requests to PHP. Generally if Nginx is not acting as a proxy to other servers you should ensure that the .php file actually exists, otherwise I could send a request like this “/avatars/image.jpg/index.php” and if PHP is configured incorrectly (which it is 99% of the time) PHP will see that /avatars/image.jpg/index.php doesn’t exist and load /avatars/image.jpg instead, with all the PHP code embedded inside of it.

    Sounds far-fetched but it’s a real exploit and I’ve personally used it as a proof of concept on a few sites with many, many daily visitors.

    Finally. #nginx on freenode is my home away from home and a great resource for Nginx. Lots of smart people there.

  2. Martin

    Thanks for chiming in. I did attempt at utilizing the document root variable however found no success. I’ll give it a shot tomorrow again.

    True that most people do not configure php well, I will certainly be adding that in. I may just drop into #nginx tomorrow as well :)

  3. A very nice guide, thansk :- )

    I have to try out nginx some day …. read about it some time ago and was not convinced it would work for me but im intrigued.

    What compatibility issues i have to look out for comparing to apache2?

    Cheers

  4. @Artur
    When comparing to apache2 there really is not that many compatibility issues, the main one is simply in how configuration is done (which is far easier). The hardest time you may have is converting over your rewrite rules which there is a section in the nginx wiki and there is also a section on the nginx.net documentation.

    Other than that, unless you are doing anything crazy with Apache, it should be a fairly straightforward switch.

  5. Philip Olson permalink

    Also note that http://php.net/install.fpm exists (although is new and not yet considered complete)

  6. bungle permalink

    Thank you,

    I followed you instructions to update my old virtual server running Ubuntu Dapper with Apache and mod_php 5.1.x to latest and greatest PHP-FPM and stable Nginx. I had about 15 sites running on Apache, and I converted nearly all of them without any pain. It was a whole lot easier than I expected. Your article helped a lot. I didn’t configure Nginx and PHP-FPM with tcp sockets, but used plain old unix sockets instead. Everything seems to run perfectly, and now I can host even more sites because of latest PHP. Dapper does not have official packages for PHP 5.3, so I would have had built PHP from sources anyway. I needed to disable fileinfo extension, because my VM didn’t have enough memory to compile that, but that’s not a big problem.

  7. Great guide.

    A few fixes to the gzip_types section, which have been lifted from the linked Slicehosts article.

    Out of the box the default mime type for JavaScript files in nginx is “application/x-javascript”, so “text/javascript” is most likely not required and won’t do anything.

    On the same tangent, xml files are served with the mime type “text/xml” – though you could argue a server side app could serve up the mime type “application/xml”.

    “application/xml+rss” should be “application/rss+xml”.

    And finally (and this one is important, being nginx+php), it would be a good idea to add “text/html”, which is the default mime type for php (as defined in php.ini).

    So assuming you are using a rather default php.ini with “text/html” as the mime type, your config outlined I don’ think will actually gzip PHP files – at least that’s what my testing showed – which could be a rather big loss as far as gzipping content goes.

    Hope that helps someone – once again, great introduction article.

  8. You should move your root directive to the server block and change that line:

    fastcgi_param SCRIPT_FILENAME /var/www/digitalstruct.com/public$fastcgi_script_name;

    to:

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

  9. Im looking into setting up my first vps with nginx and php on ubuntu, and see that as of php 5.3.3 they have added FastCGI Process Manager (FPM) SAPI.
    Does this mean that I wont have to manually compile php, leaving me to be able to do a straight install?

  10. @Stewie -
    That all depends on if a package that you are utilizing has it enabled. Otherwise you can simply build it in in the configure statement.

  11. very awesome post and blog overall. Very few people actually include the correct parameters or even a correct list of parameters for compiling. I used some hints from your post for example the init script for fpm which I couldnt find in my php source download.

    thanks and please keep sharing your insights.

  12. Phil permalink

    I was having issues getting my setup working mostly because the repo I used for my installation installed things in different directories.

    The key was to find the things that needed to be included and put the full path to the file in the .conf file. For example the default localhost.conf file that my installation created just had:

    include fastcgi_params;

    I noticed in your’s you have:
    include /usr/local/nginx/conf/fastcgi_params;

    I ended up with:
    include /etc/nginx/fastcgi_params;

    I’m just mentioning this in case it helps other folks get their server working.

Trackbacks & Pingbacks

  1. Gentoo Linux and nginx with PHP-FPM | Ole Markus

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS