Deployment of a SANE App (Part 2) - Adding an Nginx Reverse Proxy

In the post Deployment of a SANE App (Part 1), we set up an AWS EC2 box to serve our SANE (Ember.js + Sails.js) app. It was a pretty basic setup, and there are a few things we can improve. Here we will look at adding a dedicated web server which will forward requests to our Sails.js server.

What is a Reverse Proxy?

A reverse proxy is a web server that receives inbound traffic and forwards it to one or more other servers, who usually are protected from direct public access. It is called "reverse" because in a normal proxy, users from within a private network use a proxy server to gain outbound access to the internet.

Another Web Server?

Sails.js includes its own built-in web server, which is what we were using in the previous post, so why would we want to learn how to set up another web server in front of it? There are a few reasons this is a good idea.

Serve Static Assets

Serving static assets such as images and JavaScript is not what Node.js is optimized for. LinkedIn lists "Don't use Node.js for static assets" as number 3 in their list of 10 tips for nodejs performance.

Allow Load Balancing

By putting a web server in front of the Sails.js app, we can run more than one instance of the app and configure the web server to spread requests amongst them. Node.js by default is not multi-threaded, but if the EC2 box you've set up has more than one processor core, you can run multiple instances and use those cores efficiently.

Run more than one app

Maybe you have two web apps that you're working on, and you want them both to run on the same EC2 instance and respond to incoming requests for different URLs. With a reverse proxy, you can send requests to the correct server based on the name of the site or URI (e.g. http://example.com/admin could run a separate app from http://example.com/shop).

Avoid Changing Node's Permissions

In the last post, we needed to modify our server's permissions to be able to listen on low ports. While possible to do, it's clunky and has dubious security consequences.

But Wait, There's More!

For a discussion of many other benefits of a reverse proxy including SSL termination, caching, compression, and more, I recommend the excellent Benefits of a Reverse Proxy by Mike Hadlow.

Installing Nginx

The first step is to get Nginx installed and running on your EC2 instance. If you think you're going to be doing horizontal scaling and adding more EC2 instances in the future, it might be a good idea to put Nginx on its own instance, but for now we'll just assume you're starting simple and putting it on the same instance as your Node.js server.

If you just install the default version of nginx with apt-get, you're likely to wind up with a seriously out-of-date version. Instead, you can add a small step before installing, like so:

sudo add-apt-repository ppa:nginx/stable # or ppa:nginx/development  
sudo apt-get update  
sudo apt-get install nginx  

This will install a more up-to-date version from a non-official repository. As of this writing, stable is at version 1.8.0 and development is 1.9.3, which are up-to-date with the current versions of Nginx.

You could also build from source, but unless you're going to be configuring the build (unlikely), I don't see a benefit to the extra effort. If you're using an OS other than ubuntu, read the instructions for your platform at http://wiki.nginx.org/Install.

Configuring Nginx

Now that Nginx is installed, we'll want to configure it for our SANE app. This is a big topic and could probably take up a few blog posts on its own, so we are only going to skim the surface. There is a lot of good Nginx documentation out there if you want to dig in deeper.

Configuration Files

If you aren't already familiar with configuring Nginx, I highly recommended reading Understanding the Nginx Configuration File Structure and Configuration Contexts from Digital Ocean. After you've read that, come back here and we'll set ours up. For simplicity, the following snippets of code (called "contexts") can be placed right inside the http context of the main Nginx config file, usually located at /etc/nginx/nginx.conf.

Upstream

Just in case we want to use Nginx for load balancing in the future, it's not a bad idea to start off configuring upstream servers. Assuming you're running sails on the same box as Nginx, your upstream configuration might look like:

upstream sails_server {  
  server 127.0.0.1:3000;
  keepalive 64;
}

The server is the host and port where our Sails.js server will be running, which in this case is the localhost and port 3000. The keepalive will allow Nginx to keep up to 64 TCP connections to the upstream server alive.

Server
server {  
  listen        80;
  server_name   example.com;

  location / {
    proxy_pass                          http://sails_server;
    proxy_http_version                  1.1;
    proxy_set_header  Connection        "";
    proxy_set_header  Host              $host;
    proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header  X-Real-IP         $remote_addr;
  }
}

Let's step through this a bit at a time. The first two lines in the server context are specifying that this context will apply to requests which hit example.com at port 80 (http).

Location contexts are where most of the custom configuration will usually happen. What is shown here is very basic, and says that all requests which hit the root URI (/) will be proxied on to the sails_server which we set as an upstream server. The next two lines (proxy_http_version 1.1; and proxy_set_header Connection ""; enable keepalive to the upstream. To understand more about keepalives, read HTTP Keepalive Connections and Web Performance. The final three lines copy some of the headers from the client request to the request Nginx sends to the upstream.

Configuring Sails

All you need to do for sails, is set the proper port in your config/env/production.js file. You can choose any port which won't conflict with other ports on the system. A common default for node servers is 3000. Just make sure that this port matches the one you specified in the upstream context of your nginx configuration.

Summary

We added Nginx as a reverse proxy between the internet and our Sails.js application server. If everything is configured correctly, you should be able to now visit your website (example.com) and Nginx will listen to the request, proxy the request to your Sails.js server, and then send your server's response back out to the client.

Further Reading

A great place to get started for more information is the Nginx beginner's guide. From there, you can also follow links to topics like: