I talked about how I configured Unicorn for Phindee in part 3, and now I’ll cover how I configured Nginx. While Unicorn will handle requests for pages dynamically generated by Rails, Nginx will handle requests for static assets, like stylesheets, scripts, images, and fonts. If you’re wondering why I chose Nginx over Apache, see part 2 for the explanation.
All right, since there is quite a bit to cover, we’ll jump right in. We’ll start by creating a file called
nginx.conf inside our app’s
/config directory on our local computer. Here’s how mine looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Nginx has a master process managing all the workers, just like Unicorn, which are responsible for processing requests from clients. Unlike Unicorn, Nginx also has a cache loader process that checks and/or populates the cache with metadata, as well a cache manager process that’s responsible for cache expiration. Together, they keep Nginx internals running quickly and efficiently.
A Bit on Workers
If you log in to your VPS and
/etc/nginx, you’ll find a file called
nginx.conf. This is the main Nginx configuration file that Nginx will parse when it runs, and it’s the place where you can modify the number of workers available to process requests. You can do this by modifying the
worker_processes directive defined at the top of the file. It’s set to four workers by default, but I changed mine to one because that’s more than enough for a low-traffic app. It’s also possible to modify the number of connections a worker can accept by modifying the
worker_connections directive inside the
events block; I changed mine to 1024 connections.
This means that given our current configuration, our server will be able to accept a total of 1024 simultaneous connections. If you want to increase this, it’s generally best to increase the
worker_connections value before increasing the number of workers. (Remember, each worker is a single-threaded process, so whenever you increase the number of workers, you’re also increasing the total amount of memory that will be used.) But having one worker process that’s capable of handling 1024 connections is more than enough for a low-traffic app.
By the way, if you’re wondering how our own
nginx.conf file we created above will get executed,
/etc/nginx/nginx.conf already has an
include directive inside the
http block that will automatically include any files in the
/etc/nginx/sites-enabled directory, and that’s the place where we will put our own
nginx.conf file when it’s time to deploy.
Hooking up with Unicorn and Handling Redirects
Since Nginx is not capable of handling requests for pages that are dynamically generated by Rails, we need to tell it to somehow pass such requests off to Unicorn. We’ll take the first step to accomplishing this by defining an
upstream block called
unicorn, inside which we point the server to the same Unix socket that we used in our
unicorn.rb file from part 3. This is just the first step, however, and more work needs to be done to get this working, as you’ll see later. By the way, in case you’re wondering, setting
fail_timeout to 0 is necessary for Nginx to correctly handle Unicorn timing out due to its worker being killed when it takes longer than 30 seconds to respond, as specified in
server block right below the
upstream block is there to redirect a request for “www.phindee.com” to “phindee.com”. The
server_name directive specifies the URL we’re redirecting from, while the
return directive specifies where to redirect to. (Notice we’re returning a 301 status code to specify a permanent redirection.) The
$scheme variable stores the HTTP scheme (i.e. http, https), while
$request_uri stores the unmodified URI of a client request, which includes the arguments, but not the host name (e.g. “/foo/bar.php?arg=baz”).
Where the Meat Is
server block contains the main configuration. The
listen directive inside it tells Nginx to listen on port 80, and the
server_name directive right below specifies the domain name that Nginx will try to match, which is “phindee.com” in my case.
default in the
listen directive, by the way, tells Nginx to use this server block by default if it can’t find a matching domain name, which means I could technically leave out the
server_name directive completely, and everything would still work because of
default, but I like to leave it in for readability. And finally, I added the
deferred option since I’m running this on Linux, which tells Nginx to use the
TCP_DEFER_ACCEPT option to speed up performance by reducing the amount of preliminary work that happens between a client and the server.
Moving along, the
root directive specifies the directory in which Nginx will look to handle requests for static files. This is basically the directory we specified inside
unicorn.rb, except it has an additional
/public directory appended to the end of it. It corresponds to your app’s
/public directory on your local computer and is the place where your static files are and will reside. Currently, it only contains various error pages, a favicon, and a
robots.txt file for search engines. When we later deploy with Capistrano, it’ll contain all our assets as well, including stylesheets, scripts, images, and fonts.
Handling Asset Requests
Just like the
server_name directive handles requests for domain names, the
location directive handles requests for specific files and folders. The caret and tilde (^~) tells Nginx to do a regular expression match on
/assets/ and to stop searching as soon as it finds a match (see the Linode Guide to learn more).
By setting the
gzip_static directive to
on, we’re then telling Nginx to look for an already pre-compressed
.gz file before proceeding to compress it. This prevents Nginx from compressing the same file each time it is requested.
expires directive then makes the response cacheable and marks it with an expiry date set to
max, which is equivalent to December 31st, 2037. This tells browsers and any caching servers to not request these assets again until the specified date. Of course, if we make changes to our stylesheets, for example, Rails will change the filename and browsers will still receive the latest version, which will then also be cached.
expires directive, however, is an outdated method of specifying caching, and it’s recommended to use
Cache-Control header instead. The next line in the code does just that through the
add_header directive. (The reason we include
expires is to make things backward-compatible.) It’s possible, by the way, to set
Cache-Control to either
private, and I’m setting it to
public because we’re caching assets that are meant to be used by everybody, whereas
private would mean they’re unique to individual users (see Stack Overflow to learn more).
Trying to Find a Match
The next line is the
try_files directive, which is there for requests that didn’t match with any
location blocks. In our case, it tries to match non-asset requests. The
$uri variable inside it contains the current request URI, minus the arguments, protocol, and host name, so if we typed in “phindee.com/foobar” into the address bar, the
$uri would be “/foobar”, and given our
try_files directive, Nginx would try to first match a
var/www/phindee/current/public/foobar/index.html file. If it found no such file, it would then try to match the
/foobar directory itself, and if that didn’t work, it would then pass the request off to Unicorn through a named location, which is defined next through the
location directive and called
Inside the named location, the
proxy_pass directive does all the heavy lifting. We set it to
http://unicorn so that it points to the
upstream block called
unicorn, which we already defined, and the request is then handled by the Unicorn socket defined there. The two
proxy_set_header directives then append additional headers needed for Unicorn, while
proxy_redirect set to
off prevents Nginx from doing any redirect handling. (There is a sample
nginx.conf file on GitHub with comments explaining why this is necessary.)
Last Few Lines
All right, we’re down to the last two lines.
error_page makes sure that our app’s
500.html page is shown for any 500-related errors, while
keepalive_timeout tells Nginx to retain keep-alive connections (also known as persistent connections) for up to 10 seconds and close them if they exceed that time. The main concern when choosing the amount of time is mobile devices on slow networks, but I think 10 seconds should be enough.
Keep-alive connections, by the way, send multiple HTTP requests in a single connection, as opposed to opening a new connection for each request; in HTTP 1.1, all connections are persistent by default, which means stylesheets, scripts, images, and fonts, for example, would all be sent using a single connection.
These are, of course, not all the options you can specify. If you’d like to learn about the additional ones, feel free to read through the comments in the sample
nginx.conf file I mentioned earlier.
And that wraps up part 4. I will introduce Capistrano in the next post and will explain how I configured it for Phindee. If you want to be notified when it’s out, feel free to subscribe, and you’ll get it delivered to your inbox as soon as it’s released.