Rails and nginx/thin
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:

