Software & Configuration

Nginx as Load Balancer

Configure Nginx to distribute traffic between multiple backend servers. Algorithms, health checks and SSL termination.

Nginx can distribute requests between multiple backends (round-robin, least connections, IP hash) for scalability and high availability.

Architecture

Client → Nginx (Load Balancer) → Backend 1 (10.0.0.1)
                               → Backend 2 (10.0.0.2)
                               → Backend 3 (10.0.0.3)

Basic configuration (Round Robin)

nano /etc/nginx/conf.d/loadbalancer.conf
upstream backend_pool {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://backend_pool;
        proxy_http_version 1.1;
        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 X-Forwarded-Proto $scheme;

        # Timeout
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}
nginx -t && systemctl reload nginx

Load balancing algorithms

Round Robin (default)

Distributes requests in cyclic sequence. Simple and fair if backends are equivalent.

upstream backend_pool {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

Least Connections

Sends request to the server with the fewest active connections. Ideal if requests have variable duration.

upstream backend_pool {
    least_conn;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

IP Hash (sticky sessions)

Each IP is always sent to the same backend. Necessary for apps with server-side sessions.

upstream backend_pool {
    ip_hash;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

Weights (servers with different capacity)

upstream backend_pool {
    server 10.0.0.1:8080 weight=3;   # receives 3x traffic
    server 10.0.0.2:8080 weight=2;
    server 10.0.0.3:8080 weight=1;
}

Backup servers (failover)

upstream backend_pool {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080 backup;   # used only if others are down
}

Passive health check

Nginx automatically removes backends that don't respond:

upstream backend_pool {
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:8080 max_fails=3 fail_timeout=30s;
}
  • max_fails=3: after 3 consecutive errors the backend is marked down
  • fail_timeout=30s: after 30s tries again

Active health check

Active health check (health_check directive) is only available in Nginx Plus (commercial version). On open source Nginx use passive health check or an external tool like Keepalived.

Load Balancer with SSL (SSL termination)

upstream backend_pool {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Backends receive unencrypted HTTP (faster)
    location / {
        proxy_pass http://backend_pool;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
    }
}

server {
    listen 80;
    return 301 https://$host$request_uri;
}

TCP/UDP Load Balancing (stream)

To balance non-HTTP traffic (MySQL, Redis, games):

stream {
    upstream mysql_pool {
        server 10.0.0.1:3306;
        server 10.0.0.2:3306;
    }

    server {
        listen 3306;
        proxy_pass mysql_pool;
        proxy_timeout 10s;
        proxy_connect_timeout 5s;
    }
}

Stream block

The stream {} block must be at the main level of nginx.conf, NOT inside http {}.

Monitoring and debug

# Real-time status
tail -f /var/log/nginx/access.log

# See upstream where traffic comes from
log_format upstream '$remote_addr - [$time_local] "$request" '
                    '$status $upstream_addr $upstream_response_time';

# Statistics (if nginx-extras installed)
location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}
curl http://localhost/nginx_status

Output:

Active connections: 12
server accepts handled requests
 1234 1234 5678
Reading: 0 Writing: 1 Waiting: 11

Load balancing test

# Verify which backend responds to requests
for i in {1..6}; do
  curl -s http://yourdomain.com/api/whoami
  echo
done

If backends return their hostname, you'll see alternation between them.

On this page