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.

This article has 16 comments

  1. Pingback: Link dump for July 18th | The Queue Blog

  2. Fred

    this worked exceptionally well. Wondering if you have moved this to /etc/init.d/xxxx for easier handling?

  3. Pingback: Alec C4 » Blog Archive » Linkoholizm #2

  4. Rudy

    I dont know what’s wrong with the unicorn.rb file. Everything I uncommment the these lines:

    pid “~gitprj/xxx/tmp/pids/unicorn.pid”
    stderr_path “~/girprj/xxx/log/unicorn.stderr.log”
    stdout_path “~/gitprj/xxx/log/unicorn.stdout.log”

    I always get error of this:
    /home/yubuntu/.rvm/gems/ruby-1.9.2-p290/gems/unicorn-4.1.1/lib/unicorn/http_server.rb:698:in `initialize': No such file or directory – ~/gitprj/xxx/log/unicorn.stderr.log (Errno::ENOENT)

    FYI, I’m using Ubuntu 11.40, RVM and rails 3.1.0 and I am a newbie in Linux environment.

  5. Gerardo Trevino

    I have the same question like Joseph Le Brech.,

    Should we use an upstream statement for each virtual?

    Thanks

  6. Christian

    I followed your instructions but I always get an unicorn error when using upgrade:

    E, [2011-11-14T18:10:13.463457 #5925] ERROR — : old PID:4394 running with existing pid=/path_to_app/current/tmp/pids/unicorn.pid.oldbin, refusing rexec

    Do you have an idea what todo with that?

    Thx!
    Gambo

  7. MIKE

    Hi,

    I have set the same above configuration in Mac machine, but I am getting 403 forbidden error. I have changed the permission for the folder and all. But still getting same error.

    Please advice me on this issue.

    MIKE

  8. Pingback: Common Rails Tools | sayem islam / blog

  9. Pingback: Nginx + Unicorn | sayem islam / blog

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>