Nginx Basics — Part 2: Reverse Proxy

Nginx Reverse Proxy

On the web there are basically two types of computers (or more accurately, computer programs). A Server, whose job it is to serve web content. This service may include things like an HTML web page, a dynamic website with user specific contents like Facebook, web apps, or even APIs which programmers can use within other web services, like "Sign In with Google" which is used authenticate users across hundreds of web services. Point is, all of this services are provided what is known as a Server. It is the job of the server to listen for new requests, and give out appropriate response for the requests. Servers are the backbone of the web.

There's another type of program, that is the client. A client sends requests to web servers and gets a response. A client can be your web browser trying to visit a particular website. It can be a Discord App on your desktop talking to Discord servers and enabling you to chat with your friends, and it can even be a command line tools like cURL. If you use Python's request module to talk to a RESTAPI, what you are writing is an HTTP client.

Proxy and Reverse Proxy

A Proxy, in the context of the World Wide Web, means another computer that acts as a client on your behalf. So, a proxy acts as a server that recieves all of the client's HTTP requests, and then it passes those requests onto whatever actual server the request was intended for. This can be used to obfuscate the true origin of the client, to filter out unintended requests in the case of a University or an Organization's network and for a lot of other purposes. A proxy can be used by one or more clients.

A reverse proxy is a proxy for one or more servers, and it acts as a gateway for all the incoming HTTP requests. It ensures that a request reaches its corresponding backend server, and not the wrong one. Of course, it also makes sure that response gets forwarded to the appropriate client as well.

Let's try and configure Nginx to act as a reverse proxy for two web apps.

Nginx reverse proxy

If you need a primer on Nginx Configuration you can follow this blog post. But to start with a Nginx server, we will first get rid of default configuration and start with a custom server directive.

$ rm /etc/nginx/sites-enabled/default

Now, we can start adding configuration for reverse proxy. By default, Nginx will be listening on port 80 and forwarding traffic to relevant endpoints. If you wish to setup HTTPS you can complete the setup here, and then follow the instructions in this blog post.

We basically have two options here:

Both are not that different from Nginx's view point. As long as all these domain names point to the IP address that Nginx is listening on, we combination of them. In fact, we did use them to run multiple websites in blog post.

So to create a reverse proxy, a server directive would create something like this:

server {
    listen 80;
    listen [::]:80;

    server_name example.com;
    location /app1 {
        proxy_pass http://www.example.com/app1
    }
}

The proxy_pass directive tells Nginx to forward requests to the specified URI, and fetch the response and send it back to the appropriate client.

You can create a similar directive for /some/other/path and keep it going untill all your needs are met.

If you have a Python app, called app1 and another NodeJS App called app2 listening on, say, localhost on port 5000 and 5001, you can forward to them simply by using the same location directive like shown below,

location /app1 {
        proxy_pass http://localhost:5000/app1
    }
location /app2 {
        proxy_pass http://localhost:5001/app2
}

Make sure that your apps have root of their routes set at /app1 and /app2, respectively, to make sense of things.

This is how most web applications are developed and deployed. One team of developers can work on /login microservice and use the language of their choice and another can work on /dashboard and use their own framework and backend logic. And Nginx acts as a glue that brings them together in production.

If you want to have a shared hosting environment, where two completely different entities are reverse proxied by Nginx, you can do that too! You just need to create different server directives for each of them. For more details follow this blog post.

Additional configuration options

Having a reverse proxy introduces a few things we need to take care of. First would be that Nginx can become the bottleneck, since all the traffic flows through it. Fortunately, Nginx is very advanced and can handle hundreds, if not, thousands of requests on very minimal hardware. That said, the official docs show us a few decent tuneables like compression, buffering, etc that can help improve the responsiveness of your web service.

One edge case that I have personally seen is where I needed to run Google Analytics on my backend server to track the number of unique visitors. You see, if Nginx is the only 'client' that connects to your backed web app then its IP is the only IP that your web app will always see in the HTTP request headers. This is very easy to fix. We can configure Nginx to pass the client's original IP to the backend server so that the number of unique IPs visiting the website won't be just 1.

location /some/path/ {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://localhost:5000/some/path;
}

Conclusion

That is it! You now know the fundamentals of Nginx reverse proxy and are ready to tackle official docs, configure your own environment and debug your performance issues in real-time!