Sign in
← Networking: advanced
12 min · medium · Lesson 4 of 4

Load balancing (L4 and L7)

Why spread traffic across multiple servers: algorithms, health checks, and the L4 vs L7 difference.

Why more than one server

A single server hits a ceiling: it can't handle the load, or it goes down entirely, and the site is dead. The fix: run several identical copies of the app and spread requests across them. A load balancer handles the distribution.

This gives you two things at once. Scaling: more replicas, more total load you can handle. Fault tolerance: one replica goes down, the load balancer stops sending it traffic, and the rest keep working.

Concepts

What this looks like in nginx

The load balancer is often combined with the reverse proxy from the previous lesson. In nginx, replicas are listed in the upstream block, and proxy_pass gets the group name instead of an address:

upstream shop {
    server 10.0.5.10:8080;
    server 10.0.5.11:8080;
    server 10.0.5.12:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://shop;
    }
}

Without extra settings, nginx hands out requests in turn (round-robin). The server line has a weight= parameter: a replica with weight=3 gets three times as many requests as a replica with the default weight of 1.

Distribution algorithms

Without pinning, a client can land on a different replica on every request. That's why replicas are made stateless: sessions and data live in a shared place (a database, Redis), not in a specific server's memory. Then any replica can serve any request.

Pinning a client to a replica (sticky sessions) looks like a quick fix, but it doesn't remove the problem. The replica goes down, and all its sessions disappear with it.

Health checks

The load balancer needs to know which replicas are alive. There are two kinds of checks.

Active. The load balancer itself periodically pings each replica, usually a lightweight /healthz endpoint that quickly responds 200 OK if everything's fine. This is how cloud load balancers, HAProxy (option httpchk), and Kubernetes readiness probes work. In nginx, active checks (the health_check directive) exist only in the commercial NGINX Plus.

Passive. The load balancer watches the responses sent to real clients. Free nginx only knows how to do this. An attempt counts as failed if it matches the conditions of the proxy_next_upstream directive. By default that's error timeout: the connection couldn't be made, it dropped, or the replica didn't respond in time.

upstream shop {
    server 10.0.5.10:8080 max_fails=1 fail_timeout=10s;
    server 10.0.5.11:8080 max_fails=1 fail_timeout=10s;
}

The values in the example match the defaults. If max_fails failed attempts pile up within fail_timeout, nginx stops sending requests to that replica for the fail_timeout duration, then tries it again. A request that hits a failed attempt gets retried by nginx on the next replica, and the client gets a normal response.

Important detail: by default, an HTTP error response doesn't count as a failed attempt. A replica responds 503, and nginx faithfully passes that 503 to the client. To make such responses trigger a retry on another replica and pull the replica out of rotation too, you list the codes explicitly, for example proxy_next_upstream error timeout http_502;. Which codes to include depends on what the unhealthy replica actually returns.

L4 and L7

You can load balance at different layers (same as proxying).

L4 load balancer works at the TCP/UDP level. It only looks at IPs and ports, and doesn't parse the request's content. Fast, but it can't route by URL or domain. Example: a cloud Network Load Balancer (AWS NLB).

L7 load balancer works at the HTTP level. It sees the URL, headers, and cookies, and can route smartly: /api/ to one set of servers, /static/ to another; keep a client on one replica by cookie. Examples: a cloud Application Load Balancer (AWS ALB), nginx in the http block.

nginx and HAProxy can do both. In nginx, L4 balancing lives in a separate stream block at the top level of nginx.conf, next to http, not inside it:

stream {
    upstream tcp_app {
        server 10.0.5.10:5432;
        server 10.0.5.11:5432;
    }
    server {
        listen 5432;
        proxy_pass tcp_app;
    }
}

Here nginx spreads out TCP connections and doesn't know what's inside: HTTP, PostgreSQL, or something custom. In HAProxy, the same thing is toggled with the mode tcp or mode http line.

The rule is simple. If you only need to spread out TCP connections, L4 is enough. As soon as you need to decide based on HTTP content, use L7.

Common mistakes

What you'll need for the tasks

In the next tracks this connects to Kubernetes: a Service balances at L4, while Ingress and the Gateway API provide L7 routing inside the cluster.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson networking-advanced-04-load-balancing