Setting up nginx, ssl, and virtual hosts

01 February 2008

Setting up nginx, ssl, and virtual hosts

installing nginx

To install nginx you have a few options depending on the flavour of operating system you are running. You can either install it via a package manager, or download the source and compile it yourself. Using the package manager will mean you will get the latest known stable version for your OS, and have to worry less about working out dependencies or conflicts. However, you may be a version or two behind the official release.

installing nginx via yum/apt-get/rpm

If you've got a preferred package manager on your system then it should be as easy as one of the following (choose whichever is appropriate):
sudo yum install nginx
sudo apt-get install nginx

I wont go into too much detail here, the important thing to note is where the root location is. Here's is specified as /usr/share/nginx/html, which means a request to http://yoursite.com/directory/file.html is going to try and serve the file on your server from /usr/share/nginx/html/directory/file.html. To create a new virtual host lets change this definition to look more like this:

Now all requests coming in to http://www.yoursite.com will be handled by this definition, but only requests for www.yoursite.com. If you want to handle another domain within this definition you can just put an additional server name in like this:

server_name www.yoursite.com www.othersite.com;

Alternatively, if you want to have www.othersite.com serve up content from a different location you would just add another server definition:

configuring nginx for your rails app

The hosts are setup, now we can have one of them start serving some rails apps. Lets change the virtual host definition we had above for www.yoursite.com to look like the following:

server {
listen 80;
server_name www.yoursite.com;
root /var/www/apps/yoursite/current/public;
# Set the max size for file uploads to 50Mb
client_max_body_size 50M;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html last;
break;
}
location / {
# needed to forward user's IP address to rails
proxy_set_header X-Real-IP $remote_addr;
# needed for HTTPS
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect false;
proxy_max_temp_file_size 0;
# If the file exists as a static file serve it directly without
# running all the other rewrite tests on it
if (-f $request_filename) {
break;
}
# check for index.html for directory index
# if its there on the filesystem then rewite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
# this is the meat of the rails page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream mongrels
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://railsapp;
break;
}
}
}

What we have here is a series of rules that match the requested file against some regular expressions and underlying filesystem files. To tell nginx to serve any content that it finds matching the full requested path, starting in the directory defined as root we do the following:

If that doesn't work, we assume it need to be served by the rails app so we send it through using this:

if (!-f $request_filename) {
proxy_pass http://railsapp;
break;
}

But what does http://railsapp mean? No such server exists! Within nginx we need to define an upstream proxy. This will allow us to wrap all our mongrels up within a single definition, and nginx can take care of sending it through to the instances in the back end. To define the railsapp upstream mongrels we add the following outside our server definition:

setting up your nginx ssl server

What if we want to serve out site over HTTPS for security reasons? No problem! We just add a server definition that listens on the correct port (that's 443 for those of you playing along at home). So our definition would look like this:

redirecting all traffic to your ssl server

To be extra secure, we've decided that we want all traffic to be handled over SSL. Well we can use the ssl_requirement plugin in our rails app to completely prevent non-HTTPS access, just incase. We can also add this server definition to our nginx config to re-direct anybody who mistakenly links to the plain old HTTP address:

setting nginx error pages

Nginx will display some standard error messages should there be a problem, they're about as attractive as the standard ones that come with rails though. If you want to jazz them up and make the whole experience less jarring for your users then update these lines:

directing nginx log output

If you're running multiple virtual hosts, it will probably pay to give each a log file of it's own rather than having a single jumbled mess to worry about. Basically any configuration you provide in the http definition you can re-define on a per-server basis within each server definition. So simply add these lines into the relevant virtual host/server definition:

make nginx serve cache content from a custom location

Some people may cache their rail output into a custom directory (and why wouldn't you, it makes clearing the cache so much easier). If that's the case, the usual nginx/rails configuration won't work as expected. Your site will still work, you'll just not be making the most of letting nginx serve your static content. To resolve it, add these lines in before the section that passes the request to your upstream proxy: