back to blog
Nginx as a Load Balancer

Nginx as a Load Balancer

November 17, 2024

In this article, we gonna discuss how to use Nginx as a load balancer to distribute traffic across multiple Docker containers running Express servers. We will set up four separate Express servers, each returning a different response, and configure Nginx to load balance traffic between them. By the end of this tutorial, you will have a functional load balancing setup, and we will verify that the traffic is being distributed across the servers properly.

Step 1: Setting Up the Docker Environment

Before we begin building the Dockerfile, we first need to ensure that both Nginx and our virtual servers can communicate with each other within the Docker environment. This requires creating a common Docker network. The containers will be able to resolve each other by their container names within this network.

Create a Docker network named loadbalance_net using the following command:

docker network create loadbalance_net

Step 2: Create the Express Servers

Next, we will create four separate Express servers, each running inside a Docker container. Each server will return a unique message to help us identify if the load balancing is working correctly.

Create a file called index.js in your project directory with the following content for the first server:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello from server1');
});

app.listen(port, () => {
  console.log(`Server1 is running on port ${port}`);
});

Similarly, create server2.js, server3.js, and server4.js for the other servers, changing the message in the response to "Hello from server2", "Hello from server3", and "Hello from server4" respectively.

 From node:23

 WORKDIR /app

 COPY package*.json ./

 RUN npm install

 COPY . .

 CMD ["npm", "start"]

Build and run each of the server containers, connecting them to the loadbalance_net network:

docker build -t server1 .
docker run -p 3001:3000 -d --network loadbalance_net --name server1 server1

docker build -t server2 .
docker run -p 3002:3000 -d --network loadbalance_net --name server2 server2

docker build -t server3 .
docker run -p 3003:3000 -d --network loadbalance_net --name server3 server3

docker build -t server4 .
docker run -p 3004:3000 -d --network loadbalance_net --name server4 server4

These commands will start the servers and expose their respective ports on the host machine.

Step 3: Configuring Nginx as a Load Balance

Now, we will set up Nginx to act as a load balancer that will distribute the incoming traffic across these four backend servers. We'll use the Nginx Docker image and configure it to forward traffic to our backend servers.

Nginx Configuration (nginx.conf)

Here is an example of the Nginx configuration (nginx.conf), which sets up the load balancing:

upstream backend {
  server server1:3001;
  server server2:3001;
  server server3:3001;
  server server4:3001;
}
server {
  listen 80;
  include /etc/nginx/mime.types;
  location / {
    proxy_pass http://backend/;
  }
}

Docker file for Nginx

FROM nginx:stable-alpine

COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Step4: Build and Run the Nginx Load Balancer

docker build -t nginx_load_balancer .
docker run -p 3000:80 -d --network loadbalance_net --name nginx_server nginx_load_balancer

Step 5: Testing the Load Balancing

To test if load balancing is working, you can access the Nginx server by visiting http://localhost:3000 in your browser. Every time you refresh the page, you should receive a response from one of the backend servers in a round-robin fashion, like:

  • Hello from server1
  • Hello from server2
  • Hello from server3
  • Hello from server4

Server Weights

By default, NGINX distributes requests among the servers in the group according to their weights using the Round Robin method. The weight parameter to the server directive sets the weight of a server; the default is 1:

upstream backend {
    server backend1.example.com weight=5;
    server backend2.example.com;
    server 192.0.0.1 backup;
}

In the example, backend1.example.com has weight 5; the other two servers have the default weight (1), but the one with IP address 192.0.0.1 is marked as a backup server and does not receive requests unless both of the other servers are unavailable. With this configuration of weights, out of every 6 requests, 5 are sent to backend1.example.com and 1 to backend2.example.com.

Server Slow-Start

The server slow‑start feature prevents a recently recovered server from being overwhelmed by connections, which may time out and cause the server to be marked as failed again.

In NGINX Plus, slow‑start allows an upstream server to gradually recover its weight from 0 to its nominal value after it has been recovered or became available. This can be done with the slow start parameter to the server directive:

upstream backend {
    server backend1.example.com slow_start=30s;
    server backend2.example.com;
    server 192.0.0.1 backup;
}

Limiting the Number of Connections

With NGINX Plus, it is possible to limit the number of active connections to an upstream server by specifying the maximum number with the max_conns parameter.

If the max_conns limit has been reached, the request is placed in a queue for further processing, provided that the queue directive is also included to set the maximum number of requests that can be simultaneously in the queue:

upstream backend {
    server backend1.example.com max_conns=3;
    server backend2.example.com;
    queue 100 timeout=70;
}

If the queue is filled up with requests or the upstream server cannot be selected during the timeout specified by the optional timeout parameter, the client receives an error.