Basic Load Balancing with Nginx on Ubuntu

By Raman Kumar

Updated on May 24, 2025

In this tutorial, we're implementing basic load balancing with Nginx on Ubuntu 24.04 server.

Load balancing is a cornerstone of resilient, high-performance web architectures. By distributing incoming requests across multiple backend servers, we can ensure that no single machine becomes a bottleneck—and we gain redundancy in case one node goes down. In this guide, we’ll walk through how to set up a simple, yet robust, load balancing configuration using Nginx on Ubuntu. Along the way we’ll explain why each step matters and share best practices so you can adapt this setup for production environments.

Introduction

In modern web deployments, scaling horizontally—adding more servers rather than beefing up a single one—is often more cost-effective and reliable. Nginx shines as both a high-performance web server and a reverse proxy that can perform load balancing with minimal configuration. We’ll demonstrate how to:

  • Install Nginx on Ubuntu
  • Configure an upstream pool of backend servers
  • Choose and apply a load balancing method
  • Enable basic health checks and failover
  • Test and validate our setup

By the end, you’ll have a working round-robin load balancer that can be extended for more advanced scenarios.

Prerequisites

Before starting, make sure our new Ubuntu server is ready. The following components should be installed and configured:

  • A Ubuntu 24.04 installed dedicated server or KVM VPS.
  • A root user or normal user with administrative privileges.
  • Two or more backend application servers.
  • A domain name with pointing A record to server.

Basic Load Balancing with Nginx on Ubuntu

1. Prepare Your Environment

On each machine, update package lists and upgrade:

sudo apt update && sudo apt upgrade -y

Open Necessary Ports

  • Backend servers: allow traffic on port 8080 (or whatever port your app listens on).
  • Load balancer: allow HTTP (80) and optionally HTTPS (443).

Example on load balancer:

sudo ufw allow http
sudo ufw allow https    # if you plan TLS later
sudo ufw enable

2. Install Nginx on the Load Balancer

On lb.example.com, install Nginx via the official Ubuntu repositories:

sudo apt install nginx -y

Verify it’s running:

systemctl status nginx

You should see Nginx active and listening on port 80. Our load balancer is now ready to accept requests.

3. Define the Upstream Servers

Nginx uses an upstream block to group backend servers. We’ll configure a simple round-robin pool.

Open the default site configuration

sudo nano /etc/nginx/sites-available/default

Add an upstream block above the existing server block:

upstream backend {
    # Basic round-robin across two nodes
    server app1.example.com:8080;
    server app2.example.com:8080;
}

Modify the server block to proxy requests to that pool:

server {
    listen 80;
    server_name lb.example.com;

    location / {
        proxy_pass http://backend;
        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_set_header directives ensure the backend knows the original host and client IP.
  • proxy_pass http://backend; sends requests to our upstream pool named backend.

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

4. Choose a Load Balancing Method

By default, Nginx uses round-robin, sending requests alternately among servers. You can change methods easily:

Least Connections: sends traffic to the server with the fewest active connections.

upstream backend {
    least_conn;
    server app1.example.com:8080;
    server app2.example.com:8080;
}

IP Hash: ensures requests from the same client IP always go to the same backend—useful for session stickiness without cookies.

upstream backend {
    ip_hash;
    server app1.example.com:8080;
    server app2.example.com:8080;
}

Feel free to experiment; each method has its use cases. For stateless services, round-robin is sufficient and simplest.

5. Add Basic Health Checks and Failover

By default, Nginx won’t automatically remove a failing server from the pool. We can improve this by marking a server as down or specifying max_fails and fail_timeout parameters:

upstream backend {
    server app1.example.com:8080 max_fails=3 fail_timeout=30s;
    server app2.example.com:8080 max_fails=3 fail_timeout=30s backup;
}
  • max_fails=3: after three unsuccessful attempts, mark as unavailable.
  • fail_timeout=30s: duration to consider the server down.
  • backup: designate a server as a backup that only receives traffic if all primaries fail.

For more sophisticated health checks (e.g., checking a specific URI or response code), consider the Nginx Plus commercial module or open-source third-party modules like nginx_upstream_check_module.

6. Secure and Optimize Your Configuration

Enable Keepalive between Nginx and backends to reduce TCP overhead:

upstream backend {
    keepalive 16;
    server app1.example.com:8080;
    server app2.example.com:8080;
}

server {
    ...
    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://backend;
    }
}

Rate Limiting to protect backends from overload:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location / {
            limit_req zone=one burst=20 nodelay;
            proxy_pass http://backend;
            ...
        }
    }
}

TLS Termination at the load balancer: Obtain certificates via Let’s Encrypt and configure SSL in the server block so backends can operate over HTTP.

7. Test Your Setup

  • Send multiple requests (e.g., curl http://lb.example.com) and watch your backend servers’ logs to confirm requests are distributed.
  • Simulate a failure: stop the HTTP service on app1.example.com and observe Nginx routing traffic exclusively to app2.example.com.
  • Monitor with tools like nginx_status (enabled via the stub_status module) or external monitoring solutions to ensure uptime and performance.

Conclusion

Congratulations—In this tutorial, we've implemented basic load balancing with Nginx on Ubuntu 24.04 server. Our configuration can now distribute traffic across multiple servers, handle simple failure scenarios, and be extended with keepalives, rate limiting, and TLS termination. 

As we scale our applications, this pattern ensures that we maintain responsiveness, reliability, and the flexibility to add or remove nodes without downtime. Feel free to explore advanced Nginx modules and integrations with orchestration tools (e.g., Docker Swarm, Kubernetes) to further automate and enhance our load balancing infrastructure.