Load balancing distributes incoming requests across multiple backend servers, preventing any single server from becoming a bottleneck and providing horizontal scalability and fault tolerance. Nginx supports four load balancing methods natively: round-robin (the default), least connections, IP hash (session persistence), and weight-based. The upstream block defines the pool of backend servers and their weights, and Nginx automatically detects unavailable backends and routes around them. On RHEL 9, Nginx load balancing is configured with zero additional packages — the upstream module is compiled in by default. This guide covers all four balancing methods, passive health checks, connection limits, and using Nginx as a TCP/UDP load balancer with stream blocks.

Prerequisites

  • Nginx installed on RHEL 9
  • Two or more backend application servers or ports to balance across

Step 1 — Round-Robin (Default)

Round-robin distributes requests to backends in sequence. The first request goes to server 1, the second to server 2, the third to server 3, then back to server 1:

upstream myapp {
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
}

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

    location / {
        proxy_pass         http://myapp;
        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;
    }
}

Step 2 — Weighted Load Balancing

Use weight to send proportionally more traffic to more powerful servers:

upstream myapp {
    server 10.0.0.10:8080 weight=3;   # Gets 60% of traffic
    server 10.0.0.11:8080 weight=2;   # Gets 40% of traffic
}

Step 3 — Least Connections

Sends each request to the backend with the fewest active connections. Best for requests with varying processing times:

upstream myapp {
    least_conn;
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
}

Step 4 — IP Hash (Session Persistence)

Routes requests from the same client IP to the same backend server. Essential for stateful applications that store session data in memory:

upstream myapp {
    ip_hash;
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
}

Step 5 — Passive Health Checks and Failover

Nginx passively monitors backends. If a server fails to respond, it is marked as unavailable and traffic is routed to healthy servers:

upstream myapp {
    server 10.0.0.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
    # Backup server — only used when primary servers are down
    server 10.0.0.12:8080 backup;
    # Remove a server from rotation permanently without deleting the config
    server 10.0.0.13:8080 down;
}

max_fails=3 — number of consecutive failures before marking the server down. fail_timeout=30s — how long to wait before retrying a failed server.

Step 6 — Keepalive Connections to Backends

upstream myapp {
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    keepalive 32;   # Maintain up to 32 idle connections per worker to backends
}

# Required with keepalive:
location / {
    proxy_pass         http://myapp;
    proxy_http_version 1.1;
    proxy_set_header   Connection "";    # Clear the Connection header for keepalive
}

Step 7 — TCP/UDP Load Balancing with the stream Module

# Add to /etc/nginx/nginx.conf (outside the http block)
stream {
    upstream mysql_cluster {
        server 10.0.0.10:3306;
        server 10.0.0.11:3306;
    }

    server {
        listen     3306;
        proxy_pass mysql_cluster;
    }
}

Verification Checklist

nginx -t
systemctl reload nginx

# Test that requests reach different backends (check access logs on each)
for i in {1..6}; do curl -s http://app.example.com/whoami; done

Conclusion

Nginx load balancing on RHEL 9 requires only an upstream block — no additional modules or packages. Round-robin handles stateless applications; IP hash handles stateful sessions; least-conn handles mixed workloads; and the stream module extends load balancing to TCP/UDP services like MySQL. Passive health checks with max_fails and fail_timeout ensure traffic is automatically routed away from failed backends.

Next steps: How to Install and Configure Caddy Web Server on RHEL 9, How to Configure Nginx FastCGI Caching on RHEL 9, and How to Configure HAProxy for HTTP and TCP Load Balancing on RHEL 9.