Rails and nginx/thin

by James Harrison on June 12th, 2009

For the EVE people reading my blog, probably best to skip this one. If you’re one of my Rails readers, however, then this may interest you.

Charactr has been doing quite well. We originally released it deployed on Apache with Passenger (also known as mod_rails). While the performance was tolerable it wasn’t the snappiest thing in the world, and on a memory-limited VPS even with Ruby Enterprise Edition we were often running into memory limits. Right now however we’re on nginx and thin, the first production environment I’ve used this combination for.

Performance-wise, I’m impressed. Not only is the static asset hosting snappy as anything, but thin handles remarkably well and does it without chewing up much RAM. I’m pretty sure Passenger/REE would win on a box with a few gigs of RAM, but on something this small (540 megs, including the db server on the same box) the extra overhead from Passenger’s spawner was too much.

Configuration is where nginx really wins out, however…

There’s nothing wrong per-se with Apache’s configuration, but nginx makes life a whole lot easier for hosting Rails apps. First off, many thanks to Igor Sysoev for some recommendations in this config and help debugging an issue with 502 catching (So I can display a pretty page to the users while the cluster restarts; ideally we’d avoid this entirely but I’m not sure how I’d go about doing it on a budget).

# Your thin servers go here
upstream charactrapp {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
# The usual server
server {
  listen   80;
  server_name charactrapp.com www.charactrapp.com;
  access_log /opt/www/charactr/access.log;
  error_log /opt/www/charactr/error.log;
  root   /opt/www/charactr/current/public/;
  index  index.html;
  # Compress what we can, but make sure msie6 doesn't get gzip content and don't gzip tiny stuff
  gzip             on;
  gzip_min_length  1000;
  gzip_proxied     expired no-cache no-store private auth;
  gzip_types       text/plain application/xml text/css application/javascript;
  gzip_disable     msie6;
  # Catches anything that isn't sent to assets. (should catch nothing)
  location ~* (css|js|png|jpe?g|gif|ico)$ {
    expires max;
  }
  # Will render page caches if they're there, or redirect to the cluster
  location / {
    try_files $uri/index.html $uri.html $uri @charactrapp;
  }
  # The cluster is here
  location @charactrapp {
    proxy_pass http://charactrapp;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }
  # Catch servers being down and display a message. Has to be a location.
  error_page 502 /502.html;
  location = /502.html {
  }
}
server {
  listen   443;
  server_name charactrapp.com www.charactrapp.com;
  access_log /opt/www/charactr/access.log;
  error_log /opt/www/charactr/error.log;
  root   /opt/www/charactr/current/public/;
  index  index.html;
  gzip             on;
  gzip_min_length  1000;
  gzip_proxied     expired no-cache no-store private auth;
  gzip_types       text/plain application/xml text/css application/javascript;
  gzip_disable     msie6;
  # Enable SSL
  ssl                  on;
  ssl_certificate      /path/to/chained.crt; # This actually has the domain's cert, and the CA's cert/chain all in one file
  ssl_certificate_key  /path/to/app.key; # Key for the domain's cert
  keepalive_timeout    70;
  location ~* (css|js|png|jpe?g|gif|ico)$ {
    expires max;
  }
  location / {
    try_files $uri/index.html $uri.html $uri @charactrapp;
  }
  location @charactrapp {
    proxy_pass http://charactrapp;
    proxy_set_header X-FORWARDED_PROTO https; # Vital for ssl_requirement plugin to function correctly.
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }
  error_page 502 /502.html;
  location = /502.html {
  }
}
# Asset subdomain, just caches everything it serves with public Cache-Control.
server {
  listen   80;
  server_name assets.charactrapp.com;
  access_log /opt/www/charactr/access.log;
  error_log /opt/www/charactr/error.log;
  root   /opt/www/charactr/current/public/;
  index  index.html;
  gzip             on;
  gzip_min_length  1000;
  gzip_proxied     expired no-cache no-store private auth;
  gzip_types       text/plain application/xml text/css application/javascript application/x-javascript;
  gzip_disable     msie6;
  expires max;
  add_header Cache-Control public;
}

See? That’s all of Charactr’s configs. Of course, there’s still the server config but that’s pretty simple, and the default will do just fine for that.

So, overall I’m impressed with nginx. Thin I’ve used before and will be doing so with EVE Metrics 2; the combination of excellent stability with an Evented architecture hasn’t failed me yet.

Related posts:

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS