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 cd
into /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 unicorn.rb
.
The 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
The next 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.
Specifying 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.
The 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.
Using the 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 public
or 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 @unicorn
.
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.