Ruby on Rails served by unicorn with nginx for static files and reverse proxy on Ubuntu – detailed guide.

March 10, 2011

Ruby, the ever-so-popular scripting language and Rails – one of the leading web application frameworks are prone to serious performance issues if not served efficiently.
Enter unicorn, a high performance replacement for mongrels and such.
And of course, my goto tool for all things http: nginx.

This is a short step-by-step detailed guide to achieving the following:

  • RoR application listening on 2007 via unicorn
  • nginx listening on 80 forwarding all dynamic requests to the unicorn, and serving static files.

The nginx and the ruby are compiled from source, to provide the latest versions (1.9.2-p136 and 0.8.54 respectively) rather than the ones available via apt (for Ubuntu 10.10 that means: nginx 0.7.67 and ruby 1.8).

Let’s begin.

The guide assumes you are logged in as root.

root@tomtemp:~# aptitude install build-essential zlib1g-dev libssl-dev libpcre3-dev libgeoip-dev libsqlite3-dev checkinstallReading package lists... Done
Building dependency tree      
Reading state information... Done
Reading extended state information      
Initializing package states... Done
Writing extended state information... Done
The following NEW packages will be installed:
  binutils{a} build-essential checkinstall dpkg-dev{a} fakeroot{a} g++{a} g++-4.4{a} gcc{a} gcc-4.4{a} libc-dev-bin{a}
  libc6-dev{a} libgeoip-dev libgomp1{a} liblzma1{a} libpcre3-dev libpcrecpp0{a} libsqlite3-dev libssl-dev
  libstdc++6-4.4-dev{a} linux-libc-dev{a} manpages-dev{a} xz-utils{a} zlib1g-dev
The following packages will be upgraded:
  libc-bin libc6 libssl0.9.8
3 packages upgraded, 23 newly installed, 0 to remove and 75 not upgraded.
Need to get 27.7MB of archives. After unpacking 73.2MB will be used.
Do you want to continue? [Y/n/?]

After answering “y”, you will have everything necessary to continue.

Get the source packages:

root@tomtemp:~# wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p136.tar.gz
--2011-03-10 14:25:21--  ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p136.tar.gz
           => `ruby-1.9.2-p136.tar.gz'
Resolving ftp.ruby-lang.org... 221.186.184.68
Connecting to ftp.ruby-lang.org|221.186.184.68|:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done.    ==> PWD ... done.
==> TYPE I ... done.  ==> CWD (1) /pub/ruby/1.9 ... done.
==> SIZE ruby-1.9.2-p136.tar.gz ... 11155066
==> PASV ... done.    ==> RETR ruby-1.9.2-p136.tar.gz ... done.
Length: 11155066 (11M) (unauthoritative)

100%[==================================================================================>] 11,155,066   235K/s   in 2m 16s  

2011-03-10 14:27:42 (80.2 KB/s) - `ruby-1.9.2-p136.tar.gz' saved [11155066]

root@tomtemp:~# wget http://nginx.org/download/nginx-0.8.54.tar.gz
--2011-03-10 14:29:01--  http://nginx.org/download/nginx-0.8.54.tar.gz
Resolving nginx.org... 81.19.68.137
Connecting to nginx.org|81.19.68.137|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 650001 (635K) [application/octet-stream]
Saving to: `nginx-0.8.54.tar.gz'

100%[==================================================================================>] 650,001      243K/s   in 2.6s    

2011-03-10 14:29:09 (243 KB/s) - `nginx-0.8.54.tar.gz' saved [650001/650001]

So we should now have two files:

root@tomtemp:~# ls -l
total 11532
-rw-r--r-- 1 root root   650001 2010-12-14 13:06 nginx-0.8.54.tar.gz
-rw-r--r-- 1 root root 11155066 2011-03-10 14:27 ruby-1.9.2-p136.tar.gz

We’ll start with the ruby.

root@tomtemp:~# tar -xzf ruby-1.9.2-p136.tar.gz
root@tomtemp:~# cd ruby-1.9.2-p136
root@tomtemp:~/ruby-1.9.2-p136# ./configure

The configure will of course output all the relevant information, and at the end we should get:

.ext/include/x86_64-linux/ruby/config.h updated
ruby library version = 1.9.1
configure: creating ./config.status
config.status: creating Makefile

Which means everything ran correctly with no errors.

Next, compiling and installing:

root@tomtemp:~/ruby-1.9.2-p136# make && make install

Wait a while (if you are short on patience, use make -jX, where “X” is the number of your cpu cores)…

If you get the following, everything went well.

root@tomtemp:~/ruby-1.9.2-p136# ruby --version
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Moving along, we’ll take care of nginx later – first we’ll finish with all things ruby related.

root@tomtemp:~/ruby-1.9.2-p136# gem install rails

gem is the ruby module manager, and it will output its progress. Shouldn’t be any errors – we’ll do the same for unicorn.

root@tomtemp:~/ruby-1.9.2-p136# gem install unicorn
Building native extensions.  This could take a while...
Building native extensions.  This could take a while...
Successfully installed kgio-2.3.2
Successfully installed unicorn-3.4.0
2 gems installed
Installing ri documentation for kgio-2.3.2...
Installing ri documentation for unicorn-3.4.0...
Enclosing class/module 'mod' for module HttpResponse not known
Enclosing class/module 'mUnicorn' for class HttpParser not known
Enclosing class/module 'mUnicorn' for class HttpParserError not known
Installing RDoc documentation for kgio-2.3.2...
Installing RDoc documentation for unicorn-3.4.0...
Enclosing class/module 'mod' for module HttpResponse not known
Enclosing class/module 'mUnicorn' for class HttpParser not known
Enclosing class/module 'mUnicorn' for class HttpParserError not known

Despite these errors everything should be OK.

First, let’s create a new rails project at /usr/local/ named myproject and use bundle install to satisfy dependencies.

root@tomtemp:~/ruby-1.9.2-p136# cd /usr/local
root@tomtemp:/usr/local# rails new myproject
      create  
      create  README
      create  Rakefile
     --- SNIP ---
root@tomtemp:/usr/local# cd myproject/
root@tomtemp:/usr/local/myproject# bundle install
Fetching source index for http://rubygems.org/
Using rake (0.8.7)
Using abstract (1.0.0)
--- SNIP ---
Installing sqlite3 (1.3.3) with native extensions
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

Now we’ll configure unicorn.
You can manually test the application server by running the command unicorn_rails from the application’s main directory. It will listen by default on port 8080, and you can direct your browser there and you should get the default new rails application page.

root@tomtemp:/usr/local/myproject# unicorn_rails
I, [2011-03-10T15:22:00.728604 #15971]  INFO -- : listening on addr=0.0.0.0:8080 fd=3
I, [2011-03-10T15:22:00.729012 #15971]  INFO -- : worker=0 spawning...
I, [2011-03-10T15:22:00.731933 #15971]  INFO -- : master process ready
I, [2011-03-10T15:22:00.733604 #15973]  INFO -- : worker=0 spawned pid=15973
I, [2011-03-10T15:22:00.734215 #15973]  INFO -- : Refreshing Gem list
worker=0 ready

However, for a “real” server, we need a daemon.
So that things aren’t hard-coded, we will create the directory /etc/unicorn which will include a conf file for each application we want to serve.

root@tomtemp:/usr/local/myproject# mkdir /etc/unicorn
root@tomtemp:/usr/local/myproject# cd /etc/unicorn/
root@tomtemp:/etc/unicorn# ls -l
total 0
root@tomtemp:/etc/unicorn# vim myproject.conf

myproject.conf:

RAILS_ROOT=/usr/local/myproject
RAILS_ENV=production

So now we have:

root@tomtemp:/etc/unicorn# ls -l
total 4
-rw-r--r-- 1 root root 53 2011-03-10 15:33 myproject.conf

In addition, each application must include a unicorn-aware configuration unicorn.rb which will be placed in the application’s config dir.

root@tomtemp:/etc/unicorn# cd /usr/local/myproject/
root@tomtemp:/usr/local/myproject# cd config
root@tomtemp:/usr/local/myproject/config# ls -l
total 32
-rw-r--r-- 1 root root 1927 2011-03-10 15:10 application.rb
-rw-r--r-- 1 root root  191 2011-03-10 15:10 boot.rb
-rw-r--r-- 1 root root  501 2011-03-10 15:10 database.yml
-rw-r--r-- 1 root root  153 2011-03-10 15:10 environment.rb
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 environments
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 initializers
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 locales
-rw-r--r-- 1 root root 1792 2011-03-10 15:10 routes.rb
root@tomtemp:/usr/local/myproject/config# vim unicorn.rb

unicorn.rb:

# Minimal sample configuration file for Unicorn (not Rack) when used
# with daemonization (unicorn -D) started in your working directory.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
# See also http://unicorn.bogomips.org/examples/unicorn.conf.rb for
# a more verbose configuration using more features.

app_path = "/usr/local/myproject"

listen 2007 # by default Unicorn listens on port 8080
worker_processes 2 # this should be >= nr_cpus
pid "#{app_path}/tmp/pids/unicorn.pid"
stderr_path "#{app_path}/log/unicorn.log"
stdout_path "#{app_path}/log/unicorn.log"

And now we have:

root@tomtemp:/usr/local/myproject/config# ls -l
total 36
-rw-r--r-- 1 root root 1927 2011-03-10 15:10 application.rb
-rw-r--r-- 1 root root  191 2011-03-10 15:10 boot.rb
-rw-r--r-- 1 root root  501 2011-03-10 15:10 database.yml
-rw-r--r-- 1 root root  153 2011-03-10 15:10 environment.rb
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 environments
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 initializers
drwxr-xr-x 2 root root 4096 2011-03-10 15:10 locales
-rw-r--r-- 1 root root 1792 2011-03-10 15:10 routes.rb
-rw-r--r-- 1 root root  610 2011-03-10 15:36 unicorn.rb

Finally, the unicorn init script (Click to download) – credit for the script in the script’s comments.
It is to be placed in /etc/init.d and given 755 permissions.

EDIT: Dan Fox has made some interesting fixes to the script – check the fixed init script here.

unicorn_init:

#!/bin/sh
#
# init.d script for single or multiple unicorn installations. Expects at least one .conf
# file in /etc/unicorn
#
# Modified by jay@gooby.org http://github.com/jaygooby
# based on http://gist.github.com/308216 by http://github.com/mguterl
#
## A sample /etc/unicorn/my_app.conf
##
## RAILS_ENV=production
## RAILS_ROOT=/var/apps/www/my_app/current
#
# This configures a unicorn master for your app at /var/apps/www/my_app/current running in
# production mode. It will read config/unicorn.rb for further set up.
#
# You should ensure different ports or sockets are set in each config/unicorn.rb if
# you are running more than one master concurrently.
#
# If you call this script without any config parameters, it will attempt to run the
# init command for all your unicorn configurations listed in /etc/unicorn/*.conf
#
# /etc/init.d/unicorn start # starts all unicorns
#
# If you specify a particular config, it will only operate on that one
#
# /etc/init.d/unicorn start /etc/unicorn/my_app.conf

set -e

sig () {
  test -s "$PID" && kill -$1 `cat "$PID"`
}

oldsig () {
  test -s "$OLD_PID" && kill -$1 `cat "$OLD_PID"`
}

cmd () {

  case $1 in
    start)
      sig 0 && echo >&2 "Already running" && exit 0
      echo "Starting"
      $CMD
      ;;  
    stop)
      sig QUIT && echo "Stopping" && exit 0
      echo >&2 "Not running"
      ;;  
    force-stop)
      sig TERM && echo "Forcing a stop" && exit 0
      echo >&2 "Not running"
      ;;  
    restart|reload)
      sig USR2 && sleep 5 && oldsig QUIT && echo "Killing old master" `cat $OLD_PID` && exit 0
      echo >&2 "Couldn't reload, starting '$CMD' instead"
      $CMD
      ;;  
    upgrade)
      sig USR2 && echo Upgraded && exit 0
      echo >&2 "Couldn't upgrade, starting '$CMD' instead"
      $CMD
      ;;  
    rotate)
            sig USR1 && echo rotated logs OK && exit 0
            echo >&2 "Couldn't rotate logs" && exit 1
            ;;  
    *)  
      echo >&2 "Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
      exit 1
      ;;  
    esac
}

setup () {

  echo -n "$RAILS_ROOT: "  
  cd $RAILS_ROOT || exit 1
  export PID=$RAILS_ROOT/tmp/pids/unicorn.pid
  export OLD_PID="$PID.oldbin"

  CMD="/usr/local/bin/unicorn_rails -c config/unicorn.rb -E $RAILS_ENV -D"
}

start_stop () {
 
  # either run the start/stop/reload/etc command for every config under /etc/unicorn
  # or just do it for a specific one

  # $1 contains the start/stop/etc command
  # $2 if it exists, should be the specific config we want to act on
  if [ $2 ]; then
    . $2
    setup
    cmd $1
  else
    for CONFIG in /etc/unicorn/*.conf; do
      # import the variables
      . $CONFIG
      setup

      # run the start/stop/etc command
      cmd $1
    done
   fi
}

ARGS="$1 $2"
start_stop $ARGS

Let’s see if everything is OK…

root@tomtemp:/usr/local/myproject# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      754/sshd        
tcp        0      0 192.168.1.41:22         192.168.1.38:44665      ESTABLISHED 1736/0          
tcp6       0      0 :::22                   :::*                    LISTEN      754/sshd

You can the machine is listening only on port 22 for ssh connections, and that I am connected.
Let’s start the unicorn server:

root@tomtemp:/usr/local/myproject# /etc/init.d/unicorn_init start
/usr/local/myproject: Starting
root@tomtemp:/usr/local/myproject# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      754/sshd        
tcp        0      0 0.0.0.0:2007            0.0.0.0:*               LISTEN      16006/unicorn.rb -E
tcp        0      0 192.168.1.41:22         192.168.1.38:44665      ESTABLISHED 1736/0          
tcp6       0      0 :::22                   :::*                    LISTEN      754/sshd

Alright! unicorn is listening on 2007 and if you point your browser to http://$YOUR_SERVER:2007 you should get the rails default page again.

Logs are stored per application according to your configuration in unicorn.rb (stated above).

root@tomtemp:/usr/local/myproject/log# ls -l
total 8
-rw-rw-rw- 1 root root    0 2011-03-10 15:10 development.log
-rw-rw-rw- 1 root root  810 2011-03-10 15:52 production.log
-rw-rw-rw- 1 root root    0 2011-03-10 15:10 server.log
-rw-rw-rw- 1 root root    0 2011-03-10 15:10 test.log
-rw-r--r-- 1 root root 2661 2011-03-10 15:54 unicorn.log

So that’s that for unicorn. However we also require a real web server to handle the outside world.

Back to the nginx we downloaded in the beginning – decompress, configure and compile:

root@tomtemp:/usr/local/myproject/log# cd
root@tomtemp:~# tar -xzf nginx-0.8.54.tar.gz
root@tomtemp:~# cd nginx-0.8.54

The configure parameters we will use are –with-http_ssl_module –with-http_geoip_module because they are commonly useful in my opinion.

./configure --with-http_ssl_module --with-http_geoip_module

Compile:

make && make install

Let us configure nginx:

root@tomtemp:~/nginx-0.8.54# cd /usr/local/nginx/conf

This is the new nginx.conf (based partially on the official unicorn nginx recommendations):

# This is example contains the bare mininum to get nginx going with
# Unicorn or Rainbows! servers.  Generally these configuration settings
# are applicable to other HTTP application servers (and not just Ruby
# ones), so if you have one working well for proxying another app
# server, feel free to continue using it.
#
# The only setting we feel strongly about is the fail_timeout=0
# directive in the "upstream" block.  max_fails=0 also has the same
# effect as fail_timeout=0 for current versions of nginx and may be
# used in its place.
#
# Users are strongly encouraged to refer to nginx documentation for more
# details and search for other example configs.

# you generally only need one nginx worker unless you're serving
# large amounts of static files which require blocking disk reads
worker_processes 1;

# # drop privileges, root is needed on most systems for binding to port 80
# # (or anything < 1024).  Capability-based security may be available for
# # your system and worth checking out so you won't need to be root to
# # start nginx to bind on 80
user nobody nogroup; # for systems with a "nogroup"
# user nobody nobody; # for systems with "nobody" as a group instead

# Feel free to change all paths to suite your needs here, of course
pid /tmp/nginx.pid;
error_log /tmp/nginx.error.log;

events {
  worker_connections 1024; # increase if you have lots of clients
  accept_mutex off; # "on" if nginx worker_processes > 1
  # use epoll; # enable for Linux 2.6+
  # use kqueue; # enable for FreeBSD, OSX
}

http {
  # nginx will find this file in the config directory set at nginx build time
  include mime.types;

  # fallback in case we can't determine a type
  default_type application/octet-stream;

  # click tracking!
  access_log /tmp/nginx.access.log combined;

  # you generally want to serve static files with nginx since neither
  # Unicorn nor Rainbows! is optimized for it at the moment
  sendfile on;

  tcp_nopush on; # off may be better for *some* Comet/long-poll stuff
  tcp_nodelay off; # on may be better for some Comet/long-poll stuff

  # we haven't checked to see if Rack::Deflate on the app server is
  # faster or not than doing compression via nginx.  It's easier
  # to configure it all in one place here for static files and also
  # to disable gzip for clients who don't get gzip/deflate right.
  # There are other other gzip settings that may be needed used to deal with
  # bad clients out there, see http://wiki.nginx.org/NginxHttpGzipModule
#  gzip on;
#  gzip_http_version 1.0;
#  gzip_proxied any;
#  gzip_min_length 500;
#  gzip_disable "MSIE [1-6]\.";
#  gzip_types text/plain text/html text/xml text/css
#             text/comma-separated-values
#             text/javascript application/x-javascript
#             application/atom+xml;

  # this can be any application server, not just Unicorn/Rainbows!
  upstream app_server {
    # fail_timeout=0 means we always retry an upstream even if it failed
    # to return a good HTTP response (in case the Unicorn master nukes a
    # single worker for timing out).

    # for UNIX domain socket setups:
    server unix:/tmp/.sock fail_timeout=0;

    # for TCP setups, point these to your backend servers
    # server 192.168.0.7:8080 fail_timeout=0;
    # server 192.168.0.8:8080 fail_timeout=0;
    # server 192.168.0.9:8080 fail_timeout=0;
  }

  server {
    # listen 80 default deferred; # for Linux
    # listen 80 default accept_filter=httpready; # for FreeBSD
    listen 80 default;

    client_max_body_size 4G;
    server_name _;

    # ~2 seconds is often enough for most folks to parse HTML/CSS and
    # retrieve needed images/icons/frames, connections are cheap in
    # nginx so increasing this is generally safe...
    keepalive_timeout 5;

    # path for static files
    root /usr/local/myproject;

    location / {
      # an HTTP header important enough to have its own Wikipedia entry:
      #   http://en.wikipedia.org/wiki/X-Forwarded-For
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      # enable this if and only if you use HTTPS, this helps Rack
      # set the proper protocol for doing redirects:
      # proxy_set_header X-Forwarded-Proto https;

      # pass the Host: header from the client right along so redirects
      # can be set properly within the Rack application
      proxy_set_header Host $http_host;

      # we don't want nginx trying to do something clever with
      # redirects, we set the Host: header above already.
      proxy_redirect off;

      # set "proxy_buffering off" *only* for Rainbows! when doing
      # Comet/long-poll stuff.  It's also safe to set if you're
      # using only serving fast clients with Unicorn + nginx.
      # Otherwise you _want_ nginx to buffer responses to slow
      # clients, really.
      # proxy_buffering off;

      # Try to serve static files from nginx, no point in making an
      # *application* server like Unicorn/Rainbows! serve static files.
      if (!-f $request_filename) {
        proxy_pass http://localhost:2007;
        break;
      }
    }

    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /path/to/app/current/public;
    }
  }
}

The script above provides a single virtual host. It is possible to configure multiple virtual hosts, correlating with different unicorn apps on various ports. This simply requires more server{} configurations along with applicable root and proxy_pass directives.

And last but not least – the nginx init script to be placed in /etc/init.d:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: nginx init.d dash script for Ubuntu <=9.10.
# Description:       nginx init.d dash script for Ubuntu <=9.10.
### END INIT INFO
#------------------------------------------------------------------------------
# nginx - this Debian Almquist shell (dash) script, starts and stops the nginx
#         daemon for ubuntu 9.10 and lesser version numbered releases.
#
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server.  This \
#       script will manage the initiation of the \
#       server and it's process state.
#
# processname: nginx
# config:      /usr/local/nginx/conf/nginx.conf
# pidfile:     /acronymlabs/server/nginx.pid
# Provides:    nginx
#
# Author:  Jason Giedymin
#          <jason.giedymin AT gmail.com>.
#
# Version: 2.0 02-NOV-2009 jason.giedymin AT gmail.com
# Notes: nginx init.d dash script for Ubuntu <=9.10.
#
# This script's project home is:
#   http://code.google.com/p/nginx-init-ubuntu/
#
#------------------------------------------------------------------------------
#                               MIT X11 License
#------------------------------------------------------------------------------
#
# Copyright (c) 2009 Jason Giedymin, http://Amuxbit.com formerly
#                    http://AcronymLabs.com
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#------------------------------------------------------------------------------
 
#------------------------------------------------------------------------------
#                               Functions
#------------------------------------------------------------------------------
. /lib/lsb/init-functions
 
#------------------------------------------------------------------------------
#                               Consts
#------------------------------------------------------------------------------
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/nginx/sbin/nginx
 
PS="nginx"
PIDNAME="nginx"             #lets you do $PS-slave
PIDFILE=$PIDNAME.pid                    #pid file
PIDSPATH=/var/run
 
DESCRIPTION="Nginx Server..."
 
RUNAS=root                              #user to run as
 
SCRIPT_OK=0                             #ala error codes
SCRIPT_ERROR=1                          #ala error codes
TRUE=1                                  #boolean
FALSE=0                                 #boolean
 
lockfile=/var/lock/subsys/nginx
NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
 
#------------------------------------------------------------------------------
#                               Simple Tests
#------------------------------------------------------------------------------
 
#test if nginx is a file and executable
test -x $DAEMON || exit 0
 
# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
        . /etc/default/nginx
fi
 
#set exit condition
#set -e
 
#------------------------------------------------------------------------------
#                               Functions
#------------------------------------------------------------------------------
 
setFilePerms(){
 
        if [ -f $PIDSPATH/$PIDFILE ]; then
                chmod 400 $PIDSPATH/$PIDFILE
        fi
}
 
configtest() {
    $DAEMON -t -c $NGINX_CONF_FILE
}
 
getPSCount() {
    return `pgrep -f $PS | wc -l`
}
 
isRunning() {
        if [ $1 ]; then
                pidof_daemon $1
                PID=$?
 
                if [ $PID -gt 0 ]; then
                        return 1
                else
                        return 0
                fi
        else
                pidof_daemon
                PID=$?
 
                if [ $PID -gt 0 ]; then
                        return 1
                else
                        return 0
                fi
        fi
}
 
#courtesy of php-fpm
wait_for_pid () {
        try=0
 
        while test $try -lt 35 ; do
 
                case "$1" in
                        'created')
                        if [ -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
 
                        'removed')
                        if [ ! -f "$2" ] ; then
                                try=''
                                break
                        fi
                        ;;
                esac
 
                #echo -n .
                try=`expr $try + 1`
                sleep 1
        done
}
 
status(){
    isRunning
    isAlive=$?
 
    if [ "${isAlive}" -eq $TRUE ]; then
                echo "$PIDNAME found running with processes:  `pidof $PS`"
        else
                echo "$PIDNAME is NOT running."
        fi
 
 
}
 
removePIDFile(){
    if [ $1 ]; then
                if [ -f $1 ]; then
                    rm -f $1
            fi
        else
        #Do default removal
        if [ -f $PIDSPATH/$PIDFILE ]; then
                    rm -f $PIDSPATH/$PIDFILE
            fi
        fi
}
 
start() {
        log_daemon_msg "Starting $DESCRIPTION"
 
    isRunning
    isAlive=$?
 
        if [ "${isAlive}" -eq $TRUE ]; then
                log_end_msg $SCRIPT_ERROR
        else
                start-stop-daemon --start --quiet --chuid $RUNAS --pidfile $PIDSPATH/$PIDFILE --exec $DAEMON \
                -- -c $NGINX_CONF_FILE
                setFilePerms
                log_end_msg $SCRIPT_OK
        fi
}
 
stop() {
    log_daemon_msg "Stopping $DESCRIPTION"
 
    isRunning
    isAlive=$?
        if [ "${isAlive}" -eq $TRUE ]; then
                start-stop-daemon --stop --quiet --pidfile $PIDSPATH/$PIDFILE
 
        wait_for_pid 'removed' $PIDSPATH/$PIDFILE
 
                if [ -n "$try" ] ; then
                        log_end_msg $SCRIPT_ERROR
                else
                        removePIDFile
                    log_end_msg $SCRIPT_OK
                fi
 
        else
                log_end_msg $SCRIPT_ERROR
        fi
}
 
reload() {
    configtest || return $?
 
    log_daemon_msg "Reloading (via HUP) $DESCRIPTION"
 
        isRunning
        if [ $? -eq $TRUE ]; then
        `killall -HUP $PS` #to be safe
 
                log_end_msg $SCRIPT_OK
        else
                log_end_msg $SCRIPT_ERROR
        fi
}
 
quietupgrade() {
    log_daemon_msg "Peforming Quiet Upgrade $DESCRIPTION"
 
        isRunning
        isAlive=$?
        if [ "${isAlive}" -eq $TRUE ]; then
        kill -USR2 `cat $PIDSPATH/$PIDFILE`
        kill -WINCH `cat $PIDSPATH/$PIDFILE.oldbin`
 
        isRunning
        isAlive=$?
        if [ "${isAlive}" -eq $TRUE ]; then
            kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
            wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
                        removePIDFile $PIDSPATH/$PIDFILE.oldbin
 
            log_end_msg $SCRIPT_OK
        else
            log_end_msg $SCRIPT_ERROR
 
            log_daemon_msg "ERROR! Reverting back to original $DESCRIPTION"
 
            kill -HUP `cat $PIDSPATH/$PIDFILE`
            kill -TERM `cat $PIDSPATH/$PIDFILE.oldbin`
            kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
 
            wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
                        removePIDFile $PIDSPATH/$PIDFILE.oldbin
 
            log_end_msg $SCRIPT_ok
        fi
        else
                log_end_msg $SCRIPT_ERROR
        fi
}
 
terminate() {
        log_daemon_msg "Force terminating (via KILL) $DESCRIPTION"
 
    PIDS=`pidof $PS` || true
 
    [ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
 
    for i in $PIDS; do
        if [ "$i" = "$PIDS2" ]; then
                kill $i
                        wait_for_pid 'removed' $PIDSPATH/$PIDFILE
            removePIDFile
        fi
    done
 
    log_end_msg $SCRIPT_OK
}
 
destroy() {
    log_daemon_msg "Force terminating and may include self (via KILLALL) $DESCRIPTION"
    killall $PS -q >> /dev/null 2>&1
    log_end_msg $SCRIPT_OK
}
 
pidof_daemon() {
    PIDS=`pidof $PS` || true
 
    [ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
 
    for i in $PIDS; do
        if [ "$i" = "$PIDS2" ]; then
            return 1
        fi
    done
    return 0
}
 
case "$1" in
  start)
    start
        ;;
  stop)
    stop
        ;;
  restart|force-reload)
    stop
    sleep 1
    start
        ;;
  reload)
    $1
    ;;
  status)
    status
    ;;
  configtest)
        $1
        ;;
  quietupgrade)
    $1
    ;;
  terminate)
    $1
    ;;
  destroy)
    $1
    ;;
  *)
    FULLPATH=/etc/init.d/$PS
    echo "Usage: $FULLPATH {start|stop|restart|force-reload|status|configtest|quietupgrade|terminate|destroy}"
    echo "       The 'destroy' command should only be used as a last resort."
    exit 1
    ;;
esac
 
exit 0

Have Fun!

tags: , , , , , , , , , , ,
posted in scripts by tom

Follow comments via the RSS Feed | Leave a comment | Trackback URL

11 Comments to "Ruby on Rails served by unicorn with nginx for static files and reverse proxy on Ubuntu – detailed guide."

  1. links for 2011-03-13 « Bloggitation wrote:

    [...] Ruby on Rails served by unicorn with nginx for static files and reverse proxy on Ubuntu – detailed… (tags: ruby rails nginx unicorn sysadmin ubuntu) [...]

  2. Michel Pigassou wrote:

    Hi.
    What is the purpose of using Unicorn instead of for example nginx + passenger?

  3. tom wrote:

    In addition to several benchmark results, ease of configuration and unix nativeness were deciding factors.

    Also, here is a good explanation by ScottyDelicious on reddit from a thread linking to this post.

  4. Tom Kersten wrote:

    A couple of writeups I did on the same topic…one has a public EC2 AMI you can use to play around with the setup if you are interested…

    Setting up Ubuntu with Nginx, Unicorn, ree, rvm and Refined Server Setup

  5. Rob Cameron wrote:

    Hi there, is there a reason to go with USR2+QUIT in the init.d script instead of just HUP as specified in the Unicorn SIGNALS page? http://unicorn.bogomips.org/SIGNALS.html It looks like there’s only a wait of 5 seconds between starting a new master and quitting the old one, but that wouldn’t be enough time to start the new master up and start serving pages would it? Seems like some connections would be waiting a long time for the new master to spin up.

    The docs mention that having preload_app=true set in your config means you HAVE to do USR2+QUIT, but otherwise all you should need is HUP to pick up application changes.

  6. Chris Ledet wrote:

    Amazing post. Thanks for sharing!

  7. tom wrote:

    Thanks so much! Glad it could help.

  8. Alston Fernandez wrote:

    worked well on centOS

  9. tom wrote:

    Awesome – good to hear!

  10. Unicorn Rails Ubuntu init script | Dan Fox wrote:

    [...] the script wasn’t creating the file for the PID. Looking through the script I had found in this setup guide I realized that it wasn’t writing the PID anywhere. I tried to use pidof to output the PID [...]

  11. Dani Cela wrote:

    Hello, I am following you guide an am to the point of running
    /etc/init.d/unicorn_init start

    and then checking to see if the app is running on port 2007.

    The app never loads and i just get this error within my log/unicorn.log file

    unicorn_rails worker[0] -c /var/www/unicorn_tester/config/unicorn.rb -E production -D must be run inside RAILS_ROOT: #unicorn_rails worker[1] -c /var/www/unicorn_tester/config/unicorn.rb -E production -D must be run inside RAILS_ROOT: #

    Any ideas where i can start to troubleshoot this?

Leave Your Comment

 
Powered by Wordpress. Theme by Shlomi Noach, openark.org
Hosted by Evolution On-line