Get Rid Of “locahost:#port#” With NGINX Reverse Proxies


I ran in to a situation recently where I needed to have one of my web projects running on port 80 on my Mac. Normally, when i start up this project in NodeJS, it runs on port 3000. But due to an interesting interaction between this project and another project, port numbers can’t be used in my project’s URL. Now you might think that this would be easy… just tell NodeJS to use port 80 and be done with it, right? In simple scenarios, that would work just fine. But I have anything but a simple scenario. My project has multiple web server instances (all communicating with a RabbitMQ – but that’s another story) and I need all of them to run on port 80, at different URLs… all on my Mac, where I am writing code and debugging the interactions.

Right… so… multiple web server instances, all on port 80, with different DNS entries (URLs) for each. Great. There goes my day… or at least I thought so. Fortunately, NGINX to the rescue! Using reverse-proxies and a couple of entries in my /etc/hosts file, I was able to get all my servers up and running on their own port and have them appear at their own URL.

The Gist Of A Reverse Proxy

The idea behind a proxy is that it does something on your behalf, and returns the result to you – sometimes modified to suit some specific purpose. A web proxy, for example, will request things from the web and filter them, or compress them further, or do other crazy things that web proxies might do. This is typically transparent to you, the end user. You make a request as you normally would, and the proxy picks it up on your behalf, as your request makes its way out to the internet and back.

A reverse proxy is one that happens in the opposite direction than the normal “handle this request and send me the results” type of proxy. Instead of your web browser request being proxied for some purpose, it’s the web server that is being proxied. In other words, a reverse proxy handles all the incoming requests for your web server, and sends the real request to the right place to handle it. It sits in front of the real services, pushes requests to the actual service and sends responses from the service back to the original requestor.

In the case of wanting to stand up multiple services on port 80, or the need to get rid of a port # from a server and use port 80, a reverse proxy is the perfect solution. It won’t actually let you stand up multiple services on port 80, though. Instead, it will take a myriad of services that all run on different ports (3000, 3001, 4000, etc) and proxy them through port 80. Each service, running in any language that it wants to run in, will have it’s own port like you would expect. Nginx takes over port 80 and listens for specific requests. When it sees a request it knows how to handle, it takes that requests and proxies it to the actual service that can handle it.

It’s a pretty slick setup, and one that should understand if you’re going to be doing scalable web development with multiple services on a single port or URL.

Installing And Starting NGINX

This was pretty easy for me on my Mac. I just used HomeBrew… didn’t even bother checking to see if it was possible. I just opened my terminal and ran

brew install nginx

and it worked! A few minutes later, I had a working version of nginx on my box. The basics of starting and stopping nginx are:

sudo nginx

This will start it up. Note that ‘sudo’ is only required if you’re trying to take a port below 1024. Anything above that doesn’t require sudo. To stop it, run

sudo nginx -s stop

This, as you expect, stops nginx (again, the sudo bit). But you might want to have nginx run on startup with your machine. That’s easy enough. Just add an nginx.plist file to your box and tell launchctl to run it at startup:

launchctl load -F /System/Library/LaunchDaemons/nginx.plist

Using this also prevents you from having to run ‘sudo’ to start it, since it’s a system command launching it. I recommend doing this so that you don’t have to launch nginx yourself again. The only downside is that it will take over your ports that you assign it to. If you need something else on port 80, you’ll have to stop nginx.

But then, the whole point of this configuration is to proxy many services from port 80 to the actual service – so why don’t you just do that instead?

Setting Up A Reverse Proxy

There are a cubic ton of options and configuration options in nginx. It’s a bit crazy, honestly, and took me a bit to figure out. The ones that you care about, though, include setting up a server with a location and various proxy_* settings in that location. A server is really a virtual server. It tells nginx what port to listen on, what server_name to look for (HOST header), and it allows different “locations” – virtual directories – to be pointed at different actual resources or processes. In the case of a reverse proxy, the location settings will contain the proxy_* configuration.

A very basic configuration for a reverse proxy might look like this (and this is the one I’m actually using right now)

server {
listen 80;
server_name dev.signalleaf.com;
    location / {
proxy_pass http://localhost:3000/;
proxy_redirect off;
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_set_header X-NginX-Proxy true;
}
}

There are several things going on here. First off, I’m attaching nginx to port 80. Secondly, I’m telling it to listen for the server name of “dev.signalleaf.com” – this will be a HOST header that my browser sends. Then I’m telling it to watch for the root of that URL (the “/” location) and telling it how to handle the proxying of information from the original request over to my actual NodeJS/ExpressJS server that is running on localhost:3000.

And A hosts Entry

Before I can make this work on my box, though, I need one more thing: an entry in my /etc/hosts file to make dev.signalleaf.com actually work on my box. This isn’t a real DNS entry that I want to have live on the internet. It’s just an entry that I want on my box, so that I can use dev.signalleaf.com as my development URL (getting rid of the :3000 port number in the process).

127.0.0.1 dev.signalleaf.com

Let The Magic Happen

With nginx and my hosts file entry in place, I can hit http://dev.signalleaf.com on my box, and get the content that is typically served up from localhost:3000! My goal of getting rid of the port number has been achieved. From here, it won’t take a ton of extra configuration to get the additional services configured in nginx, all responding from port 80.

Getting The Real Client IP Address On A Heroku Hosted NodeJS App