Binary Talks

howto: nginx with php and Passenger (mod_rails) at once

nginx-logonginx is one hell of a webserver. It’s fast, efficient, small and reliable. According to  Netcraft’s webserver survey about 3 percent of world’s webserver are using nginx to deliver content and the amount of nginx-powered servers is growing strong.
Since I was bothered by administrating another Apache (I do this all day long) and because I was tired of letting the Apache balance on several Mongrels which is not just slow, but also quite inefficient for this low-traffic blog, I just migrated my whole environment to be served by nginx. And that wasn’t much of a problem.

Here’s how I proceeded on a Debian Lenny server, enabling nginx to serve content via PHP and Ruby through Phusion’s Passenger, also known as mod_rails, at once.

Requirements

# build-essential isn't installed by default, just in case you haven't done this yet
aptitude install bzip2 build-essential
# Required ruby packages
aptitude install ruby ruby-dev ruby1.8 rubygems libopenssl-ruby
# FastCGI packages to make nginx work with PHP
aptitude install libfcgi libfcgi-perl libfcgi
# The whole php5 package (you might not need all of it)
aptitude install php5 php5-cgi php5-common php5-dev php5-mysql php5-sqlite php5-tidy php5-xmlrpc php5-xsl php5-cgi php5-mcrypt php5-curl php5-gd php5-memcache php5-mhash php5-pspell php5-snmp php5-sqlite libmagick9-dev php5-cli

Get PHP going

To enable nginx to serve PHP-written content we have to retrieve something from the lighttpd project what is called spawnfcgi. This is necessary because nginx isn’t capable to handle PHP by default. We therefore have to get a copy of the lighttpd source, unpack, configure and make it. We then only copy the spawnfcgi binary to a local directory.

user@host ~ $ wget http://www.lighttpd.net/download/lighttpd-1.4.18.tar.bz2
user@host ~ $ tar xvjf lighttpd-1.4.18.tar.bz2
user@host ~ $ cd lighttpd-1.4.18/
user@host ~/lighttpd-1.4.18 $ ./configure
user@host ~/lighttpd-1.4.18 $ make
user@host ~/lighttpd-1.4.18 $ sudo cp src/spawn-fcgi /usr/bin/spawn-fcgi

After that we should give it a try to see whether it’s working:

user@host ~ $ sudo /usr/bin/spawn-fcgi -f /usr/bin/php-cgi -a 127.0.0.1 -p 49232 -P /var/run/fastcgi-php.pid
user@host ~ $ ps aux|grep cgi
root     31145  2.3  2.1 191944 11212 ?        Ss   23:42   0:00 /usr/bin/php-cgi
root     31147  0.0  0.8 191944  4676 ?        S    23:42   0:00 /usr/bin/php-cgi
root     31148  0.0  0.8 191944  4676 ?        S    23:42   0:00 /usr/bin/php-cgi

We then kill the processes since we’ll write an init script which starts the spawner by default. First of all, we’re writing a simple shell script which makes the exact same call as above. Then we’re putting the init script below into /etc/init.d.

/usr/bin/php5-fcgi

#!/bin/sh
/usr/bin/spawn-fcgi -f /usr/bin/php-cgi -a 127.0.0.1 -p 49232 -C 2 -P /var/run/fastcgi-php.pid

/etc/init.d/php5-fcgi

#!/bin/bash
PHP_SCRIPT=/usr/bin/php5-fcgi
RETVAL=0
case "$1" in
        start)
                echo "Starting fastcgi"
                $PHP_SCRIPT
                RETVAL=$?
  ;;
        stop)
                echo "Stopping fastcgi"
                killall -9 php-cgi
                RETVAL=$?
  ;;
        restart)
                echo "Restarting fastcgi"
                killall -9 php-cgi
                $PHP_SCRIPT
                RETVAL=$?
  ;;
        *)
                echo "Usage: php-fastcgi {start|stop|restart}"
                exit 1
  ;;
esac
exit $RETVAL

Dont’ forget to make both scripts executable:

user@host: ~ $ sudo chmod +x /usr/bin/php5-fcgi
user@host: ~ $ sudo chmod +x /etc/init.d/php5-fcgi

To start the spawner during boot we’re putting its init script into the default runlevel:

user@host: ~ $ sudo update-rc.d php5-fcgi defaults

With the appropriate nginx configuration we’d be able to serve PHP content now, but before we’ll proceed we get Ruby going.

Get Ruby going

Since the deb package of rubygems only installs version 1.2.0, we have to upgrade it to at least 1.3.1. This has to be done manually since a ‘gem update –system’ is disabled on Debian based distributions by default.

user@host: ~ $ wget http://rubyforge.org/frs/download.php/45905/rubygems-1.3.1.tgz
user@host: ~ $ tar xzf rubygems-1.3.1.tgz
user@host: ~ $ cd rubygems-1.3.1/
user@host: ~/rubygems-1.3.1 $ sudo ruby setup.rb

After these steps, a ‘gem –version’ should show that it’s upgraded to 1.3.1. So this has been done, we’re ready for installing passenger now:

sudo gem install passenger

That’ll do the job for the Ruby prerequisites. One more step is required before starting the passenger installer: creating directories.

user@host: ~ $ sudo mkdir /var/lib/nginx
user@host: ~ $ sudo mkdir /var/lib/nginx/body
user@host: ~ $ sudo mkdir /var/lib/nginx/proxy
user@host: ~ $ sudo mkdir /var/lib/nginx/fastcgi

We’re now ready to install nginx, but rather than installing it via aptitude we’ll compile it manually since otherwise passenger wouldn’t work. We’ll therefore download the source and then run the passenger installer since it’ll compile nginx for us after answering a couple of questions.

user@host: ~ $ wget http://sysoev.ru/nginx/nginx-0.6.36.tar.gz
user@host: ~ $ tar xzf nginx-0.6.36.tar.gz
user@host: ~ $ sudo passenger-install-nginx-module

What now starts is an interactive dialogue which guides us through the passenger installation including the compilation of nginx and pcre as another requirement. After we’ve hit enter required software is being checked. If you’ve did the aptitude steps in the first place everything should be allright by now, but in case the installer bothers about something that’s missing, follow the steps provided by the installer. It’s self-explanatory.
One more step and we’re asked to choose between two install methods. We choose 2 since we want to provide additional compile arguments before doing the actual work.

When we’re asked to provide the full path of the nginx source code, we type it in.

/home/user/nginx-0.6.36

As the prefix we should rather choose /usr/local/nginx instead of /opt/nginx.

/usr/local/nginx

Now comes the important part since we’re able to provide additional arguments before the actual compilation begins. nginx is highly configurable and versatile, it depends on your environment what you want to achieve. You might want to take a look at the official nginx Wiki to get information about the different modules. For me the following arguments did a good job.

--user=www-data 
--group=www-data 
--sbin-path=/usr/sbin 
--conf-path=/etc/nginx/nginx.conf 
--error-log-path=/var/log/nginx/error.log 
--pid-path=/var/run/nginx.pid 
--lock-path=/var/lock/nginx.lock 
--http-log-path=/var/log/nginx/access.log 
--http-client-body-temp-path=/var/lib/nginx/body 
--http-proxy-temp-path=/var/lib/nginx/proxy 
--http-fastcgi-temp-path=/var/lib/nginx/fastcgi 
--with-http_ssl_module 
--with-http_dav_module 
--with-http_gzip_static_module 
--with-http_stub_status_module 
--with-openssl=/usr/lib 
--without-mail_pop3_module 
--without-mail_smtp_module 
--without-mail_imap_module

Copy’n'paste of the output above won’t work because of the line break. If you want to adopt the exact same arguments stick to the output below:

--conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-http_ssl_module --without-mail_pop3_module --without-mail_smtp_module --without-mail_imap_module --sbin-path=/usr/sbin --user=www-data --group=www-data --with-http_dav_module --with-http_gzip_static_module --with-http_stub_status_module --with-openssl=/usr/lib

Pushing enter once more and nginx is finally getting compiled and installed. This usually won’t take more than 5 minutes and should return no error. After this the passenger installation provides additional information which can be ignored by now since you’ll find the exact same directives in the configuration output below.

Get nginx going

The first thing we’ll write is, of course, an init script. Copy’n'paste the output below into /etc/init.d/ and make it executable.

#! /bin/sh
 
# Description: Startup script for nginx webserver on Debian. Place in /etc/init.d and
# run 'sudo update-rc.d nginx defaults', or use the appropriate command on your
# distro.
#
# Author:       Ryan Norbauer
# Modified:     Geoffrey Grosenbach http://topfunky.com
 
set -e
 
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="nginx daemon"
NAME=nginx
DAEMON=/usr/sbin/$NAME
CONFIGFILE=/etc/nginx/nginx.conf
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
 
# Gracefully exit if the package has been removed.
test -x $DAEMON || exit 0
 
d_start() {
  $DAEMON -c $CONFIGFILE || echo -n " already running"
}
 
d_stop() {
  kill -QUIT `cat $PIDFILE` || echo -n " not running"
}
 
d_reload() {
  kill -HUP `cat $PIDFILE` || echo -n " can't reload"
}
 
case "$1" in
  start)
        echo -n "Starting $DESC: $NAME"
        d_start
        echo "."
        ;;
  stop)
        echo -n "Stopping $DESC: $NAME"
        d_stop
        echo "."
        ;;
  reload)
        echo -n "Reloading $DESC configuration..."
        d_reload
        echo "reloaded."
  ;;
  restart)
        echo -n "Restarting $DESC: $NAME"
        d_stop
        # One second might not be time enough for a daemon to stop,
        # if this happens, d_start will fail (and dpkg will break if
        # the package is being upgraded). Change the timeout if needed
        # be, or change d_stop to have start-stop-daemon use --retry.
        # Notice that using --retry slows down the shutdown process somewhat.
        sleep 5
        d_start
        echo "."
        ;;
  *)
          echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
          exit 3
        ;;
esac
 
exit 0
user@host: ~ $ sudo chmod +x /etc/init.d/nginx

Now let’s create the Debian-like directory structure under /etc/nginx for different vhosts. This enables us to activate and deactivate vhosts the easy way via symlinking.

user@host: ~ $ sudo mkdir /etc/nginx/sites-available/
user@host: ~ $ sudo mkdir /etc/nginx/sites-enabled/

What’s left is the actual configuration work of nginx. Please stick to the output below and make sure to read the comments with a leading #.
One short note: I’m not that familiar with all the aspects of nginx’s configuration yet, but the configuration below works just fine. In case you find any flaws or tweaks, I’d be glad to know about them.

/etc/init.d/nginx.conf

user  www-data;
worker_processes  1;
 
events {
    worker_connections  1024;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    #tcp_nopush     on;
 
    #keepalive_timeout  0;
    keepalive_timeout  65;
 
    #gzip  on;
 
    passenger_root /usr/lib/ruby/gems/1.8/gems/passenger-2.2.2;
    passenger_ruby /usr/bin/ruby1.8;
 
    include /etc/nginx/sites-enabled/*;
}

/etc/init.d/sites-available/site-xyz

The following is an example for a virtual server with PHP support enabled, including various location directives, e.g. for the status page of nginx which is only available when the status module has been compiled. If you took my compile arguments above, don’t worry, it’s integrated.

server {
        listen   80;
        server_name  www.example.com;
 
        access_log  /var/www/example.com/logs/access.log;
 
        location / {
                root   /var/www/example.com/htdocs;
                index  index.php;
 
                # 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;
                }
        }
 
        location ~ .php$ {
                fastcgi_pass   127.0.0.1:49232; #this must point to the socket spawn_fcgi is running on.
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME    /var/www/example.com/htdocs$fastcgi_script_name;  # same path as above
 
                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_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;
 
                # required if PHP was built with --enable-force-cgi-redirect
                # fastcgi_param  REDIRECT_STATUS    200;
        }
 
        # this location directive only works if compiled with the status module
        location /nginx_status {
                stub_status on;
                access_log   off;
                allow all;
        }
}

This is an example of a vhost configuration for a ruby powered vhost. As you can see the configuration here is much more straight-forward. Make sure that the root directive points to a directory of your project that’s called public.

server {
        listen   80;
        server_name  www.example.com;
 
        access_log  /var/www/example.com/logs/access.log
 
        root /var/www/example.com/htdocs/site-xyz/public;
        passenger_enabled on;
}

Afterwards simply symlink the configuration from /sites-enabled to /sites-available and start nginx. If everything’s ok you may see nginx’s default website and be ready to add your own PHP and Ruby applications.

user@host: /etc/nginx/sites-enabled $ sudo ln -s /etc/nginx/sites-available/site-xyz site-xyz

If you’ve got questions or suggestions, don’t hesitate to start a discussion. I’m looking forward to improve this guide if it didn’t help you with the installation and configuration of nginx. The idea of using spawn_fcgi and the init script for it comes from ElasticDog.com, the init script for nginx has been written by Ryan Norbauer and modified by Geoffrey Grosenbach.

Share and Enjoy:
  • del.icio.us
  • Digg
  • Slashdot
  • Google Bookmarks
  • LinkedIn
  • StumbleUpon
  • Reddit
  • Yigg
  • Netvibes
  • MisterWong
  • Facebook
  • HackerNews
  • Identi.ca
  • FriendFeed
  • NewsVine

5 Comments

speak up

Add your comment below, or trackback from your own site.

Subscribe to these comments.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

*Required Fields