Skip to content
Back to skills

Load Balancer

ASecurity

Load balancing with Nginx and HAProxy. Covers upstream configuration, balancing algorithms, health checks, SSL termination, session persistence, and monitoring. USE WHEN: user mentions "load balancer", "nginx upstream", "haproxy", "nginx load balancing", "round robin", "least_conn", "ip_hash", "haproxy backend", "haproxy frontend", "haproxy acl", "haproxy stats", "ssl termination", "sticky session", "upstream health check", "nginx proxy", "haproxy health check" DO NOT USE FOR: Caddy reverse...

  • 31 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
ai-agentsgobashsqlnodekubernetesapifrontendbackendsecurityperformance

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumUses curl or wget to download content

Pro shows the line behind each finding and how to fix it

Scanned September 8, 2026

npx -y skills add claude-dev-suite/claude-dev-suite --skill load-balancer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Load Balancer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Load Balancer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/claude-dev-suite-load-balancer/badge)](https://www.skillsdirectory.com/skills/claude-dev-suite-load-balancer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: load-balancer
description: |
  Load balancing with Nginx and HAProxy. Covers upstream configuration, balancing
  algorithms, health checks, SSL termination, session persistence, and monitoring.

  USE WHEN: user mentions "load balancer", "nginx upstream", "haproxy", "nginx load
  balancing", "round robin", "least_conn", "ip_hash", "haproxy backend", "haproxy
  frontend", "haproxy acl", "haproxy stats", "ssl termination", "sticky session",
  "upstream health check", "nginx proxy", "haproxy health check"

  DO NOT USE FOR: Caddy reverse proxy — use `caddy` skill,
  Traefik service mesh — use `traefik` skill,
  Kubernetes Ingress or Service resources — use `kubernetes` skill,
  DNS-level load balancing (Route 53, Cloudflare LB)
allowed-tools: Read, Grep, Glob, Write, Edit, Bash
---
# Load Balancing: Nginx & HAProxy Core Knowledge

## Nginx vs HAProxy — When to Use Each

| Dimension | Nginx | HAProxy |
|---|---|---|
| Primary role | Web server + reverse proxy + LB | Dedicated load balancer + proxy |
| Config complexity | Low-medium | Medium-high |
| HTTP modes | HTTP/1.1, HTTP/2 | HTTP/1.1, HTTP/2 (enterprise), HTTP/3 (1.9+) |
| TCP/UDP LB | Nginx Plus or stream module | Native, very mature |
| Active health checks | Nginx Plus only (open-source: passive only) | Built-in, free |
| Stats/metrics UI | Third-party (nginx-lua, stub_status) | Built-in stats page |
| Sticky sessions | Nginx Plus (cookie) or ip_hash | Stick tables (any key), free |
| Connection reuse | Keepalive to upstream | Reuse connections, queue management |
| Dynamic reconfiguration | Nginx Plus (`upstream_conf` API) | Runtime API (HAProxy 2.0+) |
| Ecosystem / docs | Very mature, massive | Mature, industry standard for pure LB |

**Use Nginx when**: you already use Nginx as your web server, you want a single tool for
serving files + proxying + LB, or your team knows Nginx.

**Use HAProxy when**: you need advanced health checks, fine-grained ACL routing, TCP load
balancing, or maximum LB performance and observability.

---

## Nginx Upstream Configuration

### Basic Upstream Block

```nginx
# /etc/nginx/nginx.conf or included conf

http {
    # Shared memory zone for upstream state across workers
    # Required for proper load balancing with multiple workers
    upstream app_backend {
        zone app_zone 256k;         # Shared state (round_robin works without it too)

        # Balancing method (default is round_robin if nothing specified)
        # least_conn;               # Route to backend with fewest active connections
        # ip_hash;                  # Sticky: same client IP always → same backend
        # hash $request_uri consistent;  # Consistent hashing by URI (good for caching)
        # random two least_conn;    # Pick 2 random servers, send to less-loaded one

        server 10.0.1.10:3000 weight=3 max_fails=3 fail_timeout=30s;
        server 10.0.1.11:3000 weight=1 max_fails=3 fail_timeout=30s;
        server 10.0.1.12:3000 weight=1 max_fails=3 fail_timeout=30s;

        # Backup server — only used when all primaries are down
        server 10.0.1.20:3000 backup;

        # Permanently excluded (maintenance)
        # server 10.0.1.13:3000 down;

        # Keepalive connections to upstream (dramatically reduces TCP overhead)
        keepalive 64;               # Max idle keepalive connections per worker
        keepalive_requests 1000;    # Max requests per keepalive connection
        keepalive_timeout 60s;
    }

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

        # Logging with upstream info
        log_format upstream_log '$remote_addr - $upstream_addr [$time_local] '
                                 '"$request" $status $body_bytes_sent '
                                 'rt=$request_time urt=$upstream_response_time';
        access_log /var/log/nginx/api_access.log upstream_log;

        location / {
            proxy_pass         http://app_backend;
            proxy_http_version 1.1;                     # Required for keepalive
            proxy_set_header   Connection "";           # Required for keepalive
            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;

            # Timeouts
            proxy_connect_timeout  5s;
            proxy_send_timeout     30s;
            proxy_read_timeout     30s;

            # Passive health check: try next upstream on errors
            proxy_next_upstream     error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
            proxy_next_upstream_timeout 10s;

            # Buffering
            proxy_buffering    on;
            proxy_buffer_size  16k;
            proxy_buffers      8 16k;
        }

        # Health check endpoint for external monitors
        location /nginx-health {
            access_log off;
            return 200 "OK\n";
        }
    }
}
```

### Active Health Check Workaround (Open-Source Nginx)

Nginx OSS only supports passive health checks. Simulate active checks with a small
service or use the `nginx_upstream_check_module` (third-party).

```bash
# Install lua-nginx-module + lua-resty-upstream-healthcheck
# OR use OpenResty (Nginx + LuaJIT bundle)
# Simple approach: use a separate monitoring tool (Consul, HAProxy) alongside Nginx
```

### SSL Termination at Nginx

```nginx
server {
    listen 443 ssl http2;
    server_name api.example.com;

    # Certificate (from Let's Encrypt / Certbot)
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # Modern TLS settings
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    location / {
        proxy_pass http://app_backend;      # Plain HTTP to backend (internal network)
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;
}
```

### Stub Status (Metrics Endpoint)

```nginx
server {
    listen 127.0.0.1:8080;   # Bind to localhost only
    location /nginx_status {
        stub_status;
        allow 127.0.0.1;
        deny all;
    }
}
```

---

## HAProxy Configuration

### Full HTTP Load Balancer Config

```haproxy
# /etc/haproxy/haproxy.cfg

global
    log         /dev/log local0 info
    log         /dev/log local0 notice notice
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     50000               # Total concurrent connections
    user        haproxy
    group       haproxy
    daemon
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners

defaults
    log         global
    mode        http                # http | tcp
    option      httplog             # Structured HTTP log format
    option      dontlognull         # Don't log health checks
    option      forwardfor          # Add X-Forwarded-For header
    option      http-server-close   # Close server-side connection after each request
    option      redispatch          # Retry on different server if session fails
    timeout     connect  5s
    timeout     client   30s
    timeout     server   30s
    timeout     http-request 10s    # Max time to receive full HTTP request
    timeout     http-keep-alive 5s
    timeout     queue   1m          # Max wait in queue when all servers full
    timeout     tunnel  1h          # For WebSocket / long-lived connections
    retries     3

#──────────────────────────────────────
# Stats page
#──────────────────────────────────────
frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:strongpassword    # CHANGE THIS
    stats show-legends
    stats show-node
    # Restrict to internal IPs
    acl internal_nets src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
    tcp-request connection reject if !internal_nets

#──────────────────────────────────────
# HTTPS frontend (SSL termination)
#──────────────────────────────────────
frontend https_in
    bind *:443 ssl crt /etc/ssl/certs/example.com.pem  # Combined cert+key PEM
    bind *:80
    http-request redirect scheme https unless { ssl_fc }

    # Define ACLs for routing
    acl host_api   hdr(host) -i api.example.com
    acl host_app   hdr(host) -i app.example.com
    acl path_admin path_beg /admin

    # Security headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    http-response set-header X-Content-Type-Options    nosniff
    http-response set-header X-Frame-Options           DENY
    http-response del-header Server

    # ACL-based routing to backends
    use_backend api_servers  if host_api
    use_backend app_servers  if host_app !path_admin
    use_backend admin_server if host_app path_admin

    default_backend app_servers

#──────────────────────────────────────
# API backend
#──────────────────────────────────────
backend api_servers
    balance leastconn               # roundrobin | leastconn | source | uri | random

    # Active HTTP health checks
    option httpchk GET /health HTTP/1.1\r\nHost:\ api.example.com
    http-check expect status 200
    default-server inter 10s fastinter 2s downinter 5s rise 2 fall 3

    # Connection limits per server
    default-server maxconn 100 maxqueue 50

    # Keepalive to backends
    option http-server-close
    timeout connect 3s
    timeout server  15s

    server api1 10.0.1.10:3000 check weight 10
    server api2 10.0.1.11:3000 check weight 10
    server api3 10.0.1.12:3000 check weight 5   # Lower weight — less powerful
    server api_backup 10.0.1.20:3000 check backup

#──────────────────────────────────────
# App backend with sticky sessions
#──────────────────────────────────────
backend app_servers
    balance roundrobin
    option httpchk GET /health
    http-check expect status 200

    # Cookie-based sticky sessions
    cookie SERVERID insert indirect nocache httponly secure

    default-server inter 10s rise 2 fall 3
    server app1 10.0.1.30:8080 check cookie app1
    server app2 10.0.1.31:8080 check cookie app2
    server app3 10.0.1.32:8080 check cookie app3

#──────────────────────────────────────
# Admin backend — IP restricted
#──────────────────────────────────────
backend admin_server
    # IP whitelist using TCP-request (set in frontend ACL)
    option httpchk GET /admin/health
    server admin1 10.0.1.50:8080 check

#──────────────────────────────────────
# TCP mode example (e.g., PostgreSQL)
#──────────────────────────────────────
frontend postgres_in
    bind *:5432
    mode tcp
    default_backend postgres_servers

backend postgres_servers
    mode tcp
    balance leastconn
    option tcp-check
    server pg_primary 10.0.2.10:5432 check
    server pg_replica 10.0.2.11:5432 check backup
```

### HAProxy Runtime API

```bash
# Enable in global section: stats socket /run/haproxy/admin.sock mode 660 level admin

# Show current server states
echo "show servers state" | socat stdio /run/haproxy/admin.sock

# Drain a server (stop sending new requests, finish existing)
echo "set server api_servers/api1 state drain" | socat stdio /run/haproxy/admin.sock

# Bring server back online
echo "set server api_servers/api1 state ready" | socat stdio /run/haproxy/admin.sock

# Change weight dynamically
echo "set server api_servers/api2 weight 20" | socat stdio /run/haproxy/admin.sock

# Show backend health
echo "show health" | socat stdio /run/haproxy/admin.sock
```

### Error Pages

```haproxy
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
```

---

## Anti-Patterns

| Anti-Pattern | Problem | Solution |
|---|---|---|
| No health checks (Nginx passive only, never configured) | Dead backends receive traffic → client errors | Configure `proxy_next_upstream` in Nginx; use `option httpchk` in HAProxy |
| `ip_hash` with clients behind shared NAT / CDN | Uneven distribution — all clients from same office go to one server | Use `least_conn` or cookie-based stickiness instead of IP hash |
| No `proxy_http_version 1.1` + `Connection ""` with Nginx keepalive | Keepalive not actually enabled — new TCP connection per request | Always pair `proxy_http_version 1.1` with `proxy_set_header Connection ""` |
| Setting `timeout client 30s` for WebSocket connections | WebSocket connections dropped after 30 seconds idle | Use `timeout tunnel 1h` (HAProxy) or `proxy_read_timeout 0` (Nginx) for WS paths |
| Not logging `$upstream_addr` and `$upstream_response_time` | Can't diagnose which backend is slow | Add to Nginx log_format; use HAProxy `%b/%s` log variables |
| `maxconn` not tuned in HAProxy global | HAProxy queues or rejects connections under load | Set `maxconn` based on RAM: ~1 MB per 1000 connections; adjust per server too |
| No `proxy_buffering` tuning in Nginx | Slow clients cause upstream to wait | Keep `proxy_buffering on`; tune `proxy_buffers` for your response sizes |
| Nginx `upstream` without `zone` directive | Round-robin per-worker only, no true least_conn across workers | Always add `zone <name> 256k` to upstream block |
| HAProxy stats page exposed on public interface | Stats reveal server IPs, health, and allow admin actions | Bind stats to `127.0.0.1` or restrict with ACL `src 10.0.0.0/8` |
| TLS termination without `ssl_session_cache` | Full TLS handshake on every request → high CPU | Add `ssl_session_cache shared:SSL:10m` and `ssl_session_timeout 1d` |

---

## Troubleshooting

| Symptom | Likely Cause | Fix |
|---|---|---|
| Uneven traffic distribution with `least_conn` | Single Nginx worker handles one backend; workers share if `zone` is set | Add `zone` directive to upstream block for shared state |
| Backend marked down immediately | Health check URL returns non-200 or times out | `curl http://10.0.1.10:3000/health` from load balancer host; adjust `rise`/`fall` thresholds |
| `502 Bad Gateway` on all requests | All backends down or proxy_pass pointing to wrong address | Check backend process; verify port; test `curl backend_ip:port` from LB |
| Session drops when scaling backends | No sticky sessions configured | Add `ip_hash` (Nginx) or `cookie` directive (HAProxy) |
| Keepalive not working (new TCP per request) | Missing `proxy_http_version 1.1` or `Connection ""` header | Add both headers; verify with `netstat -an | grep ESTABLISHED` counting |
| HAProxy shows "no server available" | All servers DOWN in health checks | `echo "show servers state" | socat stdio /run/haproxy/admin.sock`; fix health endpoint |
| Nginx returns 504 (gateway timeout) | Backend too slow; `proxy_read_timeout` too short | Increase `proxy_read_timeout`; investigate backend performance |
| SSL handshake errors | Cipher mismatch or TLS version too old | Check client TLS support; ensure `ssl_protocols TLSv1.2 TLSv1.3` |
| HAProxy rate higher than expected CPU | Too many health check connections | Increase `inter` interval: `inter 30s` for stable backends |
| X-Forwarded-For shows load balancer IP | `option forwardfor` not set (HAProxy) or `proxy_set_header X-Forwarded-For` missing (Nginx) | Add the respective directive; restart LB |

---

## Production Checklist

**Nginx:**
- [ ] `zone` directive in all `upstream` blocks
- [ ] `proxy_http_version 1.1` + `proxy_set_header Connection ""`
- [ ] `proxy_next_upstream` with appropriate error codes
- [ ] `X-Real-IP` and `X-Forwarded-For` headers set
- [ ] `keepalive` set on upstream block
- [ ] `$upstream_addr` and `$upstream_response_time` in access log format
- [ ] SSL session cache and modern TLS settings

**HAProxy:**
- [ ] `option httpchk` on all backends with correct URL and `Host` header
- [ ] `inter`, `rise`, `fall` tuned for your SLO
- [ ] `maxconn` set globally and per server
- [ ] Stats page on internal interface with auth
- [ ] Runtime API socket configured
- [ ] `timeout tunnel` set for WebSocket backends
- [ ] Error files configured for all 4xx/5xx codes
- [ ] Log format includes backend server name

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.