Nginx and passenger install in production environment

So we’ve installed ruby as described in my previous post. But we are nothing with only a ruby installation. To serve our ruby / rails apps, we’ll need a webserver. Until last year, I always used Apache to do the heavy lifting. But after some research, I decided to make the switch to Nginx. Either way, I’ll be using Nginx in combination with Passenger.

Essentials

Ok, first of all, you need to know that Nginx can’t load modules dynamically like Apache does. This means that you need to compile Nginx with all the options that you need before you can use them. So if you compile Nginx and need to activate a new module, you’ll need to recompile Nginx.

First start with installing a few essential tools for Nginx. In order to use the http rewrite-module, we need the PCRE library:

sudo aptitude install libpcre3 libpcre3-dev libpcrecpp0

Passenger

Before we install the passenger gem and start the installation process, we need to get the nginx source and another extra module I use.

I know that comes with a certain nginx install, but in most cases, isn’t the latest release. So it’s always good to have the latest release available.

wget http://nginx.org/download/nginx-1.0.14.tar.gz
tar xvzf nginx-1.0.14.tar.gz

Second thing you need is to get the headers-more module. This module makes it possible to remove certain headers in the response that Nginx sends back. Personally, I don’t like headers containing software names and versions, certainly not in a production environment.

So lets download the extra module:

wget --no-check-certificate https://github.com/agentzh/headers-more-nginx-module/tarball/v0.16 -O headers-more.tar.gz
tar xzvf headers.tar.gz

Now that all preparations are in place, we are ready to start the installation procedure:

sudo passenger-install-nginx-module

The first screen you see contains some information, so press enter to continue. The installer will start to check if all required software is available. So install it if the installer tells you you miss something.

After the check you get the 2 options: the first is an auto install. Since we are going to customize our installation, you’ll need option 2.

Next step you’ll be asked to specify the source directory where you untarred the nginx source code:

Please specify the directory: /home/michael/nginx-1.0.14

Now you’ll be asked to specify the installation directory:

Please specify a prefix directory [/opt/nginx]: /opt/nginx/nginx-1.0.14-p3.0.11

The installation directory might seem a bit odd. But let me explain it a bit in detail. Again, all self compiled software gets installed in the /opt directory. Since we want to group our nginx installs, we group them in the nginx folder.
The nginx-1.0.14-p3.0.11 folder contains the install itself. Here we are going to install nginx 1.0.14. The p3.0.11 that I appended at the end, stands for the passenger version. There are 2 reasons why I would need to recompile my nginx installation

  1. nginx got an update
  2. passenger got an update

So when I recompile nginx, I don’t want my running nginx installation to get corrupted, and all running processes to go haywire. So either way, even when nginx or passenger get updates, they will be installed in a new installation directory.

Final step is to give the extra compilation arguments:

--conf-path=/opt/nginx/shared/conf/nginx.conf --with-cpu-opt=amd64 --with-http_gzip_static_module --without-http_autoindex_module --without-http_browser_module --without-http_fastcgi_module --without-http_geo_module --without-http_empty_gif_module --without-http_map_module --without-http_ssi_module --without-http_userid_module --user=www-data --group=www-data --add-module='/home/michael/agentzh-headers-more-nginx-module-6cd7ae8'

I’m only going to explain the two most important arguments here:

  • –conf-path :  since we want to upgrade easily we’ll be placing our configuration files in a shared directory. This way there is no hastle with copying configuration files.
  • –add-module: add the path to the extra header-more module, so we can remove some headers later on.

Now that the installation is finished, create a symlink pointing to the current nginx installation:

 sudo ln -s /opt/nginx/nginx-1.0.14-p3.0.11 /opt/nginx/nginx

Configuration

My nginx configuration is pretty basic. I’ll document it inline where needed:

user                    www-data www-data;
worker_processes        1;
error_log               /var/log/nginx/error.log;
pid                     /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  # passenger and ruby location
  passenger_root              /opt/ruby/ruby/lib/ruby/gems/1.9.1/gems/passenger-3.0.11;
  passenger_ruby              /opt/ruby/ruby/bin/ruby;
  passenger_max_pool_size     15;

  include             mime.types;
  default_type        application/octet-stream;

  access_log          /var/log/nginx/access.log;
  sendfile            on;
  keepalive_timeout   65;
  client_max_body_size 100M;
  
  # we set server tokens off to hide server information
  server_tokens       off;
  # remove Server header from nginx and X-Powered-By / X-Runtime from passenger
  more_clear_headers  'Server' 'X-Powered-By' 'X-Runtime';

  # I had to increase the server hash max size. Too much server names 
  server_names_hash_max_size      1024;

  gzip                on;
  gzip_comp_level     9;
  gzip_buffers        16 8k;
  gzip_disable        "MSIE [1-6]\.";
  gzip_proxied        any;
  gzip_types          text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  charset             utf-8;
  # We save all our vhosts in the shared folder where we also store the config files. Store them in /opt/nginx/shared/sites-available/
  # This way, we symlink all enabled sites from sites-enabled. All enabled sites get included. In analogy to the Apache setup
  include             /opt/nginx/shared/sites-enabled/*;
}

Final step is saving the nginx startup script in the /etc/init.d directory:

#!/bin/sh
##
# nginx start script
##

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

if [ ! -x $DAEMON ]
then
  echo "Couldn't find $DAEMON. Please set path to DAEMON."
  exit 0
fi

set -e

case "$1" in
  start)
    echo -n "Starting $DESC: "
    start-stop-daemon --start --pidfile /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
    echo "$NAME."
    ;;
  stop)
    echo -n "Stopping $DESC: "
    start-stop-daemon --stop --pidfile /var/run/$NAME.pid --exec $DAEMON
    echo "$NAME."
    ;;
  restart|force-reload)
    echo -n "Restarting $DESC: "
    start-stop-daemon --stop --pidfile /var/run/$NAME.pid --exec $DAEMON
    sleep 1
    start-stop-daemon --start --pidfile /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
    echo "$NAME."
    ;;
  reload)
    echo -n "Reloading $DESC configuration: "
    start-stop-daemon --stop --signal HUP --pidfile /var/run/$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

Make sure the path and daemon paths are correct.

vhost

Last thing is a vhost example of a typical Ruby on Rails vhost. You can find extra info inline:

server {
  listen       80;
  server_name  vhost.be;

  # create a separate access and error log per vhost
  access_log      /var/log/nginx/vhost.www.log;
  error_log       /var/log/nginx/vhos.www.error.log;

  location / {
    # vhost root for rails apps is the public folder and enable passenger
    root /srv/vhost/www/current/public;
    passenger_enabled on;

    # the following if statements is to check if the requests asks for a static file. If it does, 
    # then it needs to be served by Nginx directly.
    # but if it doesn't exist, we want nginx to return a 404 error and not hit the Rails stack
    set $asset F;
    if ($request_uri ~* "^/(images|javascripts|stylesheets|flash|media|assets)/.*$") {
      set $asset T;
    }

    if (!-f $request_filename) {
      set $asset "${asset}T";
    }

    if ($asset = TT)
    {
      return 404;
      break;
    }
  }

  # we want to compress the static files as much as possible and also cache the assets 
  # this is needed for rails 3
  location ~^/(assets)/ {
    root /srv/netronix/www/current/public;
    gzip_static on; # to serve pre-gzipped version
    expires max;
    add_header Last-Modified "";
    add_header ETag "";
    add_header  Cache-Control public;
    break;
  }
}
# auto start passenger process when we restart nginx
passenger_pre_start http://www.netronix.be;

So, by now you should have a fully operational Ruby and Nginx-Passenger stack. Hope everything worked out and if you have any suggestions, do let me know