Sleek Design Superhero

is here to save the day and gather the best tutorials and articles on web development and design.

Configuring Nginx and Unicorn

Posted on Jul 09 in General, Ruby on Rails, Scalability | (13) Comments

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.

DeliciousFacebookDiggTechnorati FavoritesSlashdotRedditStumbleUponDZoneShare

Related Posts

Comments

  1. [...] Sleekd » Configuring Nginx and Unicorn – good set of notes on getting nginx and unicorn talking [...]

  2. erich | Sep 22nd, 2010

    great! thank you.

  3. John | Oct 4th, 2010

    Excellent, good guide! Just what I was after :)

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

  5. Thanks for the feedback. I didn’t write an init script for this but others did, you can check out this page.

  6. using:
    try_files $uri $uri/ @unicorn; (with location @unicorn being the proxy_pass)

    would be better than using the if statement for unknown files.

  7. This looks very simple :)

    how would you go about having more than one app or virtual host with a unicorn process each?

    Cheers.

  8. [...] Configuring nginx and unicorn [...]

  9. Rudy | Sep 1st, 2011

    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.

  10. Mosselman | Oct 29th, 2011

    Where should I put the config file? Also, should I have apt-gotten installed the nginx server?

  11. Gerardo Trevino | Oct 31st, 2011

    I have the same question like Joseph Le Brech.,

    Should we use an upstream statement for each virtual?

    Thanks

  12. Christian | Nov 14th, 2011

    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

  13. Christian | Nov 14th, 2011

    sorry, the error wasn’t related to this guide

Leave a Comment