3 min read

Configuring Nginx and Unicorn

I first heard about Unicorn in an interview with 37Signal's server admin Mark Imbriaco and it made me really curious. There are a few great resources explaining how Unicorn works and a neat benchmark comparing Mongrel, Passenger and Unicorn. I will share my experience playing with Nginx and Unicorn on a Debian Lenny box.

Assuming you have your Rails stack configured (I am running Ruby 1.8.7 on Rails 2.3.8) you can use RubyGems to install unicorn :

 debian:/# gem install unicorn

We can check if everything went smooth by checking the version:

 debian:/# unicorn -v unicorn v1.0.0

There are two binaries: unicorn and unicorn_rails. Executing them with -h parameter seems to produce the same options but as Tyler Bird mentions in his article, unicorn_rails config options are inspired by the script/server behavior and accepts options like -E for specifying the Rails environment.

As the guys from Github point out, Nginx -> Unicorn is a good alternative to Nginx -> HA Proxy -> Other backends (Mongrel, Thin, Passenger). The role of HA Proxy behind Nginx was to health check the backend cluster and make sure it is ready to connections. What's nice about Unicorn is that the backend cluster (worker processes) are pulling requests from a shared socket as opposed to being pushed requests from a load balancer.

In short, what I will show you is how to create a Rails test app, create a unicorn.rb configuration file and configure Nginx to communicate with the Unicorn socket.

Let's create the test app in the /var/rails folder:

debian:/var/rails# rails testapp

Next we create the unicorn.rb file in our /testapp/config folder. Most of the options I used are found in the sample unicorn.rb configuration file on Unicorn's website. Here's the unicorn.rb file as it applies to my setup:

worker_processes 2 
working_directory "/var/rails/testapp/" 

# This loads the application in the master process before forking 
# worker processes 
# Read more about it here: 
# http://unicorn.bogomips.org/Unicorn/Configurator.html 

preload_app true 
timeout 30 

# This is where we specify the socket. 
# We will point the upstream Nginx module to this socket later on 

listen "/var/rails/testapp/tmp/sockets/unicorn.sock", :backlog => 64 
pid "/var/rails/testapp/tmp/pids/unicorn.pid" 

# Set the path of the log files inside the log folder of the testapp 
stderr_path "/var/rails/testapp/log/unicorn.stderr.log"
stdout_path "/var/rails/testapp/log/unicorn.stdout.log" 

before_fork do |server, worker| 
# This option works in together with preload_app true setting 
# What is does is prevent the master process from holding 
# the database 
 connection defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! 
end 

after_fork do |server, worker| 
# Here we are establishing the connection after forking worker # 
 processes defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection 
end

Next we are going to configure Nginx. My setup is really simple - normally you would have those settings configured on a virtual host but for the sake of this example I will add all the configurations in the main nginx.conf file.

As with the unicorn configuration, you should check out the sample nginx.conf file on Unicorn's website, which is almost identical to what I am presenting here.

worker_processes 1; 
user nobody nogroup; 
pid /tmp/nginx.pid; 
error_log /tmp/nginx.error.log; 

events { 
  worker_connections 1024; # increase if you have lots of clients # Set this to on if you have more than 1 working processes 
  # This will allow only one child to watch the pollset and accept 
  # a connection to a socket 
  accept_mutex off; # "on" if nginx worker_processes > 1 
} 

http { 
  include mime.types; 
  default_type application/octet-stream; 
  access_log /tmp/nginx.access.log combined; 

  # This tells Nginx to ignore the contents of a file it is sending 
  # and uses the kernel sendfile instead 
  sendfile on; 

  # Set this to on if you have sendfile on 
  # It will prepend the HTTP response headers before 
  # calling sendfile() 
  tcp_nopush on; 

  # This disables the "Nagle buffering algorithm" (Nginx Docs) 
  # Good for websites that send a lot of small requests that 
  # don't need a response 
  tcp_nodelay off; 

  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; 

  upstream unicorn_server { 
    # This is the socket we configured in unicorn.rb server  
    unix:/var/rails/testapp/tmp/sockets/unicorn.sock fail_timeout=0; 
   } 

  server { 
    listen 80; 
    client_max_body_size 4G; 
    server_name _; 

    keepalive_timeout 5; 

    # Location of our static files root /var/rails/testapp/public; 
    location / { 
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
      proxy_set_header Host $http_host; proxy_redirect off; 
      # If you don't find the filename in the static files 
      # Then request it from the unicorn server 
      if (!-f $request_filename) { 
        proxy_pass http://unicorn_server; break; 
      } 
    } 

    error_page 500 502 503 504 /500.html; 

    location = /500.html { 
     root /var/rails/testapp/public; 
    } 
  } 
}

This is it. Now we restart Nginx to apply the settings and start unicorn from our /var/rails/testapp folder. Notice I added the -D parameter to daemonize the process. You can start it without it if you want to see how unicorn processes the requests live.

debian:/var/rails/testapp$ unicorn_rails -c config/unicorn.rb -D

Now open your browser or use links and navigate to http://localhost. You should see the Rails welcome page.