L4 vs L7 load balancing
Published
The numbers come from the OSI model. Layer 4 is the transport layer: TCP and UDP, IP addresses and ports. Layer 7 is the application layer: HTTP, with its methods, paths, headers and cookies. A load balancer working at layer 4 makes decisions about connections knowing only addresses and ports. One working at layer 7 opens the envelope and makes decisions about each request, knowing exactly what it is.
A building analogy: an L4 balancer is the security guard who sends each arriving car to a free parking level without knowing who is inside. An L7 balancer is the receptionist who reads your appointment letter and walks you to the right department.
You run 6 replicas of a gRPC service behind a layer 4 load balancer. The dashboard shows two replicas at 90% CPU and four nearly idle, even though traffic is steady. Nothing is broken. What is going on?
Layer 4: fast and blind#
An L4 balancer sees a new TCP connection (or a UDP flow) to 203.0.113.10:443, picks a
backend using the client's address and port, and from then on forwards every packet of that
connection to the same backend. It does not decrypt anything and does not know whether the
bytes are HTTP, Postgres or a game protocol.
Because it does so little per packet, it is extremely fast and cheap. It can run in the kernel (Linux IPVS), in eBPF/XDP at the network card driver (Meta's open source Katran), or as a fleet of software routers (Google's Maglev, described in a 2016 paper). A few machines handle millions of packets a second. Some setups use direct server return: the response goes from the backend straight to the client, skipping the balancer entirely. AWS's Network Load Balancer and Google Cloud's network load balancer are L4.
What it cannot do: route /api and /images to different services, retry a failed request
on another backend, add headers, or balance individual requests inside one long lived
connection.
Layer 7: smart and heavier#
An L7 balancer terminates the connection: it completes the TLS handshake, reads the HTTP request, and opens its own connection to a backend. Now it can:
- Route by content: host name, path, header, cookie, method.
api.acme.in/v2/*to the new service,?beta=1users to a canary. - Balance per request, including each request inside a multiplexed HTTP/2 or gRPC connection.
- Retry and time out per request, and take a backend out after a few failed requests rather than waiting for a health check.
- Sticky sessions by cookie, header rewriting, compression, caching, WAF rules, and per route metrics.
The cost is CPU for TLS and parsing, a little added latency, and the fact that it sees plaintext, so it is part of your security boundary. AWS's Application Load Balancer, NGINX, HAProxy in HTTP mode, Envoy and Traefik are L7.
The gRPC trap#
HTTP/2 and gRPC clients open one connection and keep it for hours, sending thousands of requests over it. To an L4 balancer that is one decision, made once. A handful of busy clients with long lived connections pin all their traffic to a handful of backends, and new replicas you add get nothing until clients reconnect. That is the answer to the question at the top.
The fixes: an L7 proxy such as Envoy in front of the service, client side load balancing built into gRPC (the client resolves all replicas and spreads requests itself), a service mesh that does it in the sidecar, or at minimum a maximum connection age on the server so clients reconnect and rebalance every few minutes.
How real systems stack them#
Large platforms use both, in layers. A tier of L4 balancers (Maglev, Katran, an NLB) takes the raw internet traffic and spreads connections across a fleet of L7 proxies (Envoy, NGINX, an ALB), which terminate TLS and route each request to the right service. The L4 tier is cheap and almost never changes; the L7 tier is where routing rules, retries and canaries live.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Layer 4 | Very fast, cheap, protocol agnostic (databases, games, any TCP or UDP), TLS can stay end to end. | One decision per connection, no content routing, no per request retries, poor balance with long lived connections. | The outermost tier at scale, non HTTP traffic, when backends must terminate TLS themselves. |
| Layer 7 | Per request balancing, path and header routing, retries, canaries, cookies, WAF, rich metrics. | CPU for TLS and parsing, extra latency, sees plaintext, HTTP only. | In front of web apps and APIs, between microservices, anything gRPC or HTTP/2. |
- AWS ELBAWS: NLB is L4, ALB is L7
- EnvoyL7 proxy, gRPC aware, used in meshes
- NGINXL7 (and L4 with the stream module)
- HAHAProxyBoth modes: tcp (L4) and http (L7)
- MaMaglev / KatranGoogle and Meta's software L4 balancers
- KubernetesServices are L4, Ingress and Gateway API are L7
In the interview#
Checkpoint#
1. Why are gRPC replicas unevenly loaded behind an L4 balancer?
2. What can an L7 balancer do that an L4 one cannot?
3. Why is source IP hashing a poor stickiness method for mobile users in India?
- L4: connections, IPs and ports. Fast, blind, protocol agnostic.
- L7: HTTP requests. Routing, retries, per request balance, costs CPU.
- Long lived HTTP/2 and gRPC connections defeat L4 balancing.
- At scale: L4 tier in front of an L7 proxy tier.
- Stickiness: cookie at L7, not source IP behind NAT.
A layer 4 load balancer works with connections, using only IP addresses and ports; it picks a backend once per TCP connection or UDP flow and forwards packets, which makes it very fast, cheap and protocol agnostic, but it cannot route by content or balance requests inside a connection. A layer 7 balancer terminates TLS and reads each HTTP request, so it can route by host, path, header or cookie, balance every request including those multiplexed on HTTP/2, retry and run canaries, at the cost of CPU and latency. The classic pitfall is gRPC behind L4: long lived connections pin traffic to a few replicas, which I fix with an L7 proxy, client side balancing or a maximum connection age. Large systems stack them: an L4 tier like Maglev or an NLB spreading connections over L7 proxies like Envoy that route requests. For stickiness I prefer cookies at L7, because source IPs behind carrier grade NAT are shared.