The Complete NGINX on Ubuntu Series: Part 7A – Load Balancing Methods and Strategies

The Complete NGINX on Ubuntu Series: Part 7A – Load Balancing Methods and Strategies

Welcome to Part 7A of our comprehensive NGINX on Ubuntu series! Building on our reverse proxy foundation, we’ll now explore load balancing methods and strategies to distribute traffic efficiently across multiple backend servers.

Understanding Load Balancing

Load balancing distributes incoming requests across multiple backend servers, improving performance, reliability, and scalability. NGINX offers several algorithms, each suited for different scenarios.

graph TD
    A[Client Requests] --> B[NGINX Load Balancer]
    B --> C{Load Balancing Algorithm}
    
    C -->|Round Robin| D[Server 1]
    C -->|Round Robin| E[Server 2]
    C -->|Round Robin| F[Server 3]
    C -->|Round Robin| G[Server 4]
    
    H[Benefits] --> I[High Availability]
    H --> J[Better Performance]
    H --> K[Scalability]
    H --> L[Fault Tolerance]
    
    style B fill:#e1f5fe
    style C fill:#fff3e0
    style D fill:#e8f5e8
    style E fill:#e8f5e8
    style F fill:#e8f5e8
    style G fill:#e8f5e8

Load Balancing Methods

1. Round Robin (Default)

Distributes requests evenly across all servers in sequential order. Best for servers with similar specifications.

# Round robin load balancing (default behavior)
upstream backend_roundrobin {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
    
    # Keep alive connections for better performance
    keepalive 32;
}

2. Least Connections

Routes requests to the server with the fewest active connections. Ideal for applications with long-running requests.

# Least connections - ideal for long-running requests
upstream backend_leastconn {
    least_conn;
    
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
    
    keepalive 32;
}

3. IP Hash (Session Persistence)

Uses client IP address to determine which server handles the request, ensuring the same client always goes to the same server.

# IP hash - ensures same client always goes to same server
upstream backend_iphash {
    ip_hash;
    
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
}

# Alternative: Hash with custom key
upstream backend_customhash {
    hash $request_uri consistent;  # Can also use $cookie_sessionid
    
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
}

4. Weighted Distribution

Assigns different weights to servers based on their capacity, sending more traffic to higher-spec servers.

# Weighted distribution based on server capacity
upstream backend_weighted {
    # High-performance server gets more traffic
    server 192.168.1.10:3000 weight=5;
    server 192.168.1.11:3000 weight=3;
    server 192.168.1.12:3000 weight=2;
    # Low-spec server gets less traffic
    server 192.168.1.13:3000 weight=1;
    
    keepalive 32;
}

5. Random Load Balancing

Randomly selects servers, with optional bias toward least connected servers (NGINX 1.15.1+).

# Random selection (NGINX 1.15.1+)
upstream backend_random {
    random two least_conn;  # Pick 2 random servers, choose least connected
    
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
}

Server Parameters and Options

NGINX provides various server parameters to control load balancing behavior and handle server failures.

# Advanced server configuration with parameters
upstream backend_advanced {
    least_conn;
    
    # Primary servers with health monitoring
    server 192.168.1.10:3000 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000 weight=3 max_fails=3 fail_timeout=30s;
    
    # Secondary server with different settings
    server 192.168.1.12:3000 weight=2 max_fails=2 fail_timeout=20s;
    
    # Backup server (only used when all others fail)
    server 192.168.1.13:3000 backup;
    
    # Server in maintenance (temporarily disabled)
    server 192.168.1.14:3000 down;
    
    # Connection settings
    keepalive 32;
    keepalive_requests 100;
    keepalive_timeout 60s;
}

Server Parameter Explanation

  • weight=N: Relative weight for load distribution (default: 1)
  • max_fails=N: Number of failed attempts before marking server unavailable
  • fail_timeout=time: Time to wait before retrying a failed server
  • backup: Server only used when all primary servers are down
  • down: Permanently mark server as unavailable
  • slow_start=time: Gradually increase traffic to recovering server

Creating Load Balanced Virtual Hosts

# Complete load balanced virtual host
sudo nano /etc/nginx/sites-available/loadbalanced.example.com
upstream web_backend {
    least_conn;
    
    server 192.168.1.10:3000 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:3000 weight=2 max_fails=2 fail_timeout=20s;
    server 192.168.1.13:3000 backup;
    
    keepalive 32;
}

server {
    listen 80;
    server_name loadbalanced.example.com;
    
    location / {
        proxy_pass http://web_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 X-Forwarded-Proto $scheme;
        
        # Load balancer timeouts
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;
        
        # Add load balancer headers for debugging
        add_header X-Upstream-Server $upstream_addr;
        add_header X-Upstream-Response-Time $upstream_response_time;
    }
    
    # Health check endpoint
    location /lb-health {
        access_log off;
        return 200 "Load balancer is healthy\n";
        add_header Content-Type text/plain;
    }
}

Session Persistence Strategies

graph TD
    A[Client with Session] --> B[Load Balancer]
    B --> C{Persistence Method}
    
    C -->|IP Hash| D[Hash Client IPSame Server Always]
    C -->|Cookie Hash| E[Hash Session CookieConsistent Routing]
    C -->|URI Hash| F[Hash Request URICache-friendly]
    
    D --> G[Server Pool]
    E --> G
    F --> G
    
    H[Session Storage] --> I[Shared Redis]
    H --> J[Database Sessions]
    H --> K[Sticky Sessions]
    
    style B fill:#e1f5fe
    style C fill:#fff3e0
    style G fill:#e8f5e8
    style H fill:#e8f5e8

Cookie-Based Session Persistence

# Session persistence using cookies
upstream backend_sessions {
    hash $cookie_sessionid consistent;
    
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 192.168.1.13:3000;
}

server {
    listen 80;
    server_name sessions.example.com;
    
    location / {
        proxy_pass http://backend_sessions;
        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;
        
        # Ensure session cookie is passed
        proxy_set_header Cookie $http_cookie;
    }
}

User-Type Based Routing

# Route based on user type header
map $http_x_user_type $backend_pool {
    default     backend_general;
    "premium"   backend_premium;
    "api"       backend_api;
    "admin"     backend_admin;
}

upstream backend_general {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
}

upstream backend_premium {
    server 192.168.1.20:3000;
    server 192.168.1.21:3000;
}

upstream backend_api {
    server 192.168.1.30:3000;
    server 192.168.1.31:3000;
}

upstream backend_admin {
    server 192.168.1.40:3000;
}

server {
    listen 80;
    server_name routed.example.com;
    
    location / {
        proxy_pass http://$backend_pool;
        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;
    }
}

Testing Load Balancing

# Enable the load balanced site
sudo ln -s /etc/nginx/sites-available/loadbalanced.example.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload NGINX
sudo systemctl reload nginx

# Add to hosts file for testing
echo "127.0.0.1 loadbalanced.example.com" | sudo tee -a /etc/hosts

# Test round-robin distribution
for i in {1..10}; do
    curl -s -H "Host: loadbalanced.example.com" http://localhost/ | grep -o "Server: [0-9.]*"
done

# Test with session persistence
for i in {1..5}; do
    curl -s -H "Host: sessions.example.com" -H "Cookie: sessionid=user123" http://localhost/ | grep -o "Server: [0-9.]*"
done

# Load testing with Apache Bench
ab -n 1000 -c 10 -H "Host: loadbalanced.example.com" http://localhost/

Monitoring Load Distribution

# Create simple load monitoring script
sudo nano /usr/local/bin/monitor-load.sh
#!/bin/bash

# Simple load balancing monitor
echo "=== Load Balancing Monitor ==="
echo "Timestamp: $(date)"
echo

# Check backend server responses
SERVERS=("192.168.1.10:3000" "192.168.1.11:3000" "192.168.1.12:3000")

for server in "${SERVERS[@]}"; do
    if curl -f -s --max-time 3 "http://$server/health" > /dev/null 2>&1; then
        echo "✅ $server - Healthy"
    else
        echo "❌ $server - Unhealthy"
    fi
done

echo
echo "Active connections:"
netstat -an | grep :80 | grep ESTABLISHED | wc -l

# Make executable
# sudo chmod +x /usr/local/bin/monitor-load.sh

Common Load Balancing Scenarios

Scenario 1: Web Application with Database

# Web tier load balancing
upstream web_tier {
    least_conn;
    server 192.168.1.10:3000 weight=2;
    server 192.168.1.11:3000 weight=2;
    server 192.168.1.12:3000 weight=1 backup;
}

# API tier load balancing  
upstream api_tier {
    round_robin;
    server 192.168.1.20:4000;
    server 192.168.1.21:4000;
    server 192.168.1.22:4000;
}

Scenario 2: Microservices Architecture

# User service
upstream user_service {
    server 192.168.1.30:3001;
    server 192.168.1.31:3001;
}

# Order service
upstream order_service {
    server 192.168.1.40:3002;
    server 192.168.1.41:3002;
}

# Payment service
upstream payment_service {
    server 192.168.1.50:3003;
    server 192.168.1.51:3003;
}

What’s Next?

Great! You’ve learned the fundamental load balancing methods and how to configure them in NGINX. You can now distribute traffic across multiple servers using different algorithms based on your needs.

Coming up in Part 7B: Advanced Health Checks, Monitoring, and Failover Strategies

References


This is Part 7A of our 22-part NGINX series. You’re mastering load balancing fundamentals! Part 7B will cover advanced health checks and monitoring. Questions? Share them in the comments!

Written by:

347 Posts

View All Posts
Follow Me :