Library
9 min read

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.

Your answer

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=1 users 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.

Same traffic, two balancers
One decision per connection. TLS passes through untouched to the backend. If the client sends 1,000 requests over that connection, all 1,000 go to backend A.

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.

Load across 6 replicas
55
25
15
5.0
0.0
0.0
0
1
2
3
4
5
Replica 0 carries 55.0% (even would be 16.7%)
Four gRPC clients, each with one long lived connection, sending 55%, 25%, 15% and 5% of the traffic. L4 balanced the connections perfectly evenly and the requests terribly. Two replicas get nothing at all.

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.

ChoiceWhat you gainWhat you payPick it when
Layer 4Very 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 7Per 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.
Names you will hear
  • 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#

Interviewer
You put a load balancer in front of your services. L4 or L7?
They want a decision tied to the traffic, plus awareness of the layered setup.
You
For HTTP APIs, L7: I need path based routing to different services, per request balancing because clients use HTTP/2, retries on idempotent requests, and canary routing by header. At large scale I would put an L4 tier in front, spreading connections across the L7 proxies, because L4 is cheap and handles raw connection volume and DDoS absorption well. For a non HTTP protocol like a Postgres proxy or a game server on UDP, L4 only.
Each choice is justified by a capability the other lacks.
Interviewer
A user's requests must keep going to the same server. How?
You
I would first try to remove that need by keeping session state in Redis. If it is truly needed, at L7 I would use a cookie the balancer sets, which survives NAT. At L4 the only handle is the source IP, and with carrier grade NAT many users share one IP, so it skews badly.
L4 or L7?
Send /api/* to the API service and /* to the frontend
Balance a fleet of MySQL read replicas
Route 5% of users with header x-canary: 1 to the new version
Absorb a 2 Tbps flood in front of everything else

Checkpoint#

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?

Worth memorising
  • 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.
Say this in 60 seconds

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.