Intro

Hello and welcome back. Yesterday, we launched our Node APIs with PM2 and then learned how to manage and monitor our processes. Now we have one major thing left to do and that is to setup Nginx itself. We'll be using Nginx to proxy and load balance the Node1 and Node2 droplets. Enough chit chat, let's get straight to work.

Getting Started

To begin, we're going to grab the ip address for our Nginx droplet from the Digital Ocean dashboard then use it to ssh into it from the terminal.

(video of grabbing the ip address)

sudo freebsd@your_nginx_ip
sudo nano /usr/local/etc/nginx

Good, our next move is going to be to set up our Nginx proxy server. For those of you who are unfamiliar with how Nginx works, let's take a look at a few of the examples already provided by Nginx itself.

The first thing we need to take note of is that Nginx works in blocks much like you would see in C or C++ code. More specifically, we have simple directives and block directives. A simple directive would be something like this...

pid /logs/nginx.pid;

A simple directive is just a rule that you have written, much like a statement in a line of code. You have the name of the function you are essentially calling, followed by one or more parameters that are separated by spaces, and the line ends with a semicolon.

So what about block directives? First, take a look at the http block with the curly braces. That is a block directive. It functions much like you would expect from other languages like C or C++. A block directive allows you to define numerous simple directives that apply to a specific context. In this case, http is used as a core directive for handling all http networking issues. If you look inside of http however, you'll see a serious of server blocks. Each of these blocks are defining actual web servers. So if you were wanting to use Nginx to host a series of websites, this would be where you defined each website.

Now of course, with Nginx giving all these example server blocks, it gives you a chance to see how all of this works. You can see a normal webserver here, an odd virtual host here, then you have an HTTPS example located here.

Knowing all of this, we can now have some idea of what is going on and can begin writing our proxy/load balancing code. Let's begin.

# Reverse Proxy + Load Balancer
upstream nodeapp {
    server node1_ip_address:3000;
    server node2_ip_address:3000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://nodeapp;
    }
}

Essentially, that's it. We now have a load balancer and proxy in place. Technically, this would work. However, we need more than just this if we want Nginx to operate in an optimum manner. For example, when Nginx receives the initial request from the client, it will modify some headers for performance reasons. For this reason, we need to add a simple directive that will ensure the host header is set to one of our Node APIs and not the Nginx server itself. To do this, let's make a quick change to our code.

# Reverse Proxy + Load Balancer
upstream nodeapp {
    server node1_ip_address:3000;
    server node2_ip_address:3000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_set_header HOST $host;
        proxy_pass http://nodeapp;
    }
}

There, that should do it, however we aren't done. We need to also forward some IP information to the Node APIs because in a production scenario, our Node APIs may want to know where their requests are coming from. So let's get this handled.

# Reverse Proxy + Load Balancer
upstream nodeapp {
    server node1_ip_address:3000;
    server node2_ip_address:3000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://nodeapp;
    }
}

Excellent, now it's worth taking a moment to discuss the load balancing algorithms that we have at our disposal. The first one is the default algorithm used by Nginx called round robin. It ensures that your load between the servers is equally distributed. Pretty simple. Next we want to take a look at least connected, optionally weighted. In this algorithm, it will send requests to the server with the least connections. Helping to lighten the load if either server is becoming overloaded. If you wanted to use it, the code would like this.

upstream nodeapp {
    least_conn;
    server node1_ip_address:3000 weight=1;
    server node2_ip_address:3000 weight=1;
}

Not bad, now it's time to take a look at another algorithm called ip hash. To understand this algorithm, we need to take into consideration that many servers are going to be stateful. Many applications use sessions for their authentication schemes for example. In this case, either of the 2 previous algorithms would be unsuitable because if a client initiated a session with one server, then Nginx routes the request to another server, that other server will have no clue what this is about and then you have a problem. This is where ip hash comes in. It will continue to load balance but it will make sessions sticky to a particular ip address. So if a client opens a session that was started with one server, then their requests will continue to be routed to that particular server. Enabling this is remarkably easy, let's write it.

upstream nodeapp {
    ip_hash;
    server node1_ip_address:3000;
    server node2_ip_address:3000;
}

Again, simple to set up. Now, let's go ahead and return our configuration to the round robin algorithm.

(brief video showing everything being returned back to normal)

Good, that should do it for configuring our load balancer and proxy in Nginx. Before we close things out, let's also handle some final configuration for Nginx in general. First, we need to configure our error and access logs. First, let's take care of the error logs in the current file.

error_log  /var/log/nginx/error.log info;

Good, this gives us the location of the error log file and how much error log information will be printed. We can change the priorities as follows.

  1. debug
  2. info
  3. notice
  4. warn
  5. error
  6. crit
  7. alert
  8. emerg

Essentially, we can choose the level of what kind of errors get logged into our error log file.

Now let's handle our access logs right quick.

server {
    listen 80;
    server_name localhost;
    access_log /var/log/nginx/access.log main;

    location / {
        proxy_pass http://nodeapp;
    }
}

And that's it, our logs are configured. Now it would also be wise to configure HTTPS however, due to costs of acquiring a domain I won't be able to walk you through that. However, we have the Nginx configuration example for HTTPS and you can follow the steps given by Let's Encrypt in order to receive your certificate that will get you up and running with HTTPS. It isn't bad to set up and you will be up and running fairly quick.

Conclusion

And that is it! We have our Nginx server configured and ready to receive requests. Our system is tied together and ready to do its work. Scaling out shouldnt be hard as we can always boost our CPU power or add more Node servers. Meanwhile on MLab, we can always scale up our database as needed.

Thank you so much for going through this whole tutorial and it is my sincere hope that you were able to get something good out of this to advance your career or favorite pet project. Best wishes to you all.

Helpful Links

  1. https://nginx.org

Alex Allen

Alex is an independent developer who is obsessed with both performance and information security. When not writing code, Alex is either playing guitar or working in his garden.

  1. Comments for Setup Nginx

You must login to comment

You May Also Like