Start here: where does the balancer make its decision?#
TL;DRthe 30-second version
- A load balancer can route at two different depths. An L4 (transport-layer) balancer forwards TCP/UDP by IP address and port without reading the bytes inside. An L7 (application-layer) balancer terminates the connection, reads the HTTP request, and routes on its contents.
- L4 is fast and protocol-agnostic. It moves packets at near wire speed and doesn't care whether they carry HTTP, a database protocol, or a game stream. But one connection is pinned to one backend for its whole life, and the balancer can't route by URL, header, or cookie because it never sees them.
- L7 is smarter and costs more. Because it reads the request, it can route by path or header, retry a single failed request, rewrite headers, and terminate TLS. It pays for that with CPU per request and the work of decrypting HTTPS to see inside.
- The distinctions that follow all come from this one fork: TLS handling (L7 decrypts, L4 passes through), sticky sessions (L4 pins a connection, L7 pins with a cookie), and health checks (L4 asks 'is the port open?', L7 asks 'does /health return 200?').
- Big deployments don't choose β they chain. An L4 layer out front absorbs raw connections and DDoS, then hands off to an L7 layer that does the smart routing.
Say a request is on its way to your service. It shows up at the balancer as a stream of bytes on a network connection. Somewhere inside those bytes is an HTTP request β a method, a URL path like /api/checkout, a Host header, maybe a session cookie. But the balancer doesn't have to read any of that to do its job. It can route the connection purely on the envelope: the source IP address and port, the destination IP and port. Everything the balancer needs to forward a packet is right there on the outside.
So there's a choice, and it's made before you ever pick a balancing algorithm. The balancer can stay on the outside β forward the raw connection by its addresses and never open it. Or it can open the connection, read the HTTP request in full, and route on what the request actually says. The first is cheaper and works for any protocol. The second is more capable and only works because it understands the protocol inside. This is the layer-4 versus layer-7 decision, and the two names come from where each one operates in the network stack.
How each one works#
An L4 balancer works like a fast forwarder. A client opens a TCP connection to the balancer's address. The balancer picks a backend, and from then on it shuttles packets between that client and that backend without looking inside them. It rewrites the addresses on each packet β swapping its own address for the chosen backend's β and passes the bytes along untouched. Because it never parses the payload, it has no idea whether the connection carries HTTP, a database query, or video. One connection maps to one backend, and that mapping holds for the entire life of the connection.
That last part matters. An L4 balancer decides once, when the connection opens, and is then committed. If the client sends a hundred HTTP requests down one reused connection, all hundred go to the same backend, because to the L4 balancer they're just more bytes on a connection it already routed. It can balance connections across the pool, but it can't balance the requests inside them.
An L7 balancer works the other way. It terminates the connection β meaning the client's connection ends at the balancer, and the balancer opens its own separate connection onward to a backend. In between, it reads the full HTTP request. Now it can route on anything the request contains: send /api/* to the API servers and /images/* to a static tier, pin a Host header to a tenant's pool, or route a slice of traffic to a new version to test it before rolling it out (a canary release). And because it makes a fresh routing decision for every request, a hundred requests on one client connection can fan out to a hundred different backends.
- L4 path: client opens a TCP connection to the balancer. The balancer picks a backend by IP and port, rewrites packet addresses, and forwards bytes both ways for the life of the connection. It never reads the HTTP inside.
- L7 path: client opens a connection that ends at the balancer. The balancer reads the full HTTP request, chooses a backend based on its path/headers/cookies, and forwards the request over its own connection to that backend β a new decision per request.
TLS: the balancer either reads the request or it can't#
Almost all real traffic is HTTPS, which means the HTTP request is wrapped in TLS β the encryption layer that keeps the bytes secret in transit (walked through in the /tls/sim simulator). This is where the L4/L7 split gets sharp, because an L7 balancer's whole job is reading the request, and an encrypted request is unreadable until someone decrypts it.
So an L7 balancer must terminate TLS: it holds the server's certificate and private key, completes the TLS handshake with the client, and decrypts the traffic. Only then can it read the path and headers it needs to route on. This is called TLS termination, and it's genuinely useful beyond routing β one place to manage certificates, and the backends can skip the cost of encryption on the internal hop. The cost is that the balancer does the decryption work, and the traffic travels in plaintext from the balancer to the backend unless you re-encrypt it.
An L4 balancer has no such requirement, because it isn't trying to read anything. It forwards the encrypted bytes straight through to the backend, and the backend terminates TLS itself. This is called TLS passthrough: the balancer never holds the private key and never sees plaintext. It's simpler and more private, but it's the reason an L4 balancer can't route by URL β the URL is inside the encryption it refuses to open.
| L4 (passthrough) | L7 (termination) | |
|---|---|---|
| Who decrypts | The backend | The balancer |
| Holds the private key | No β never sees plaintext | Yes |
| Can route by URL/header | No (they're encrypted) | Yes |
| Cost | None β forwards bytes | Decryption CPU per connection |
PredictYou want a load balancer that spreads TCP connections across backends but never has access to your customers' decrypted data β the private key must live only on the backends. L4 or L7, and why?
Hint: Which balancer has to decrypt to do its job, and which one never opens the bytes?
L4, doing TLS passthrough. Because an L4 balancer routes on the connection's IP and port and never reads the payload, it can forward the encrypted bytes straight to a backend without ever decrypting them. The private key stays on the backends; the balancer never sees plaintext. An L7 balancer can't do this β its entire purpose is reading the HTTP request, which means it must terminate TLS and therefore must hold the key and see the decrypted data. The trade you accept for that privacy is capability: since the L4 balancer can't see the URL or headers, you lose path-based routing, header rewrites, and per-request retries. That's the recurring shape of this whole topic β L4 gives up visibility to stay simple and private.
What each one costs#
The cost gap comes from how often each balancer makes a decision and how much work that decision takes. An L4 balancer decides once per connection and does almost nothing per packet β rewrite a couple of addresses and forward. There's no parsing and no buffering, so it moves traffic at close to raw network speed. This is why the biggest L4 balancers advertise millions of new connections per second and single-digit-microsecond added latency: the per-packet work is tiny and constant.
An L7 balancer decides once per request, and each decision costs more. It has to buffer enough of the request to parse the HTTP, match it against routing rules, and β for HTTPS β decrypt it first. Reusing one client connection for many requests means many decisions on that one connection, each with its own parse. None of this is expensive in absolute terms, but it's real per-request CPU, and it's why an L7 tier needs more machines to push the same raw throughput as an L4 tier.
State is the other cost. Both kinds of balancer usually track live connections so return traffic goes back the right way, which makes the balancer a stateful chokepoint β every connection flows through it, and if it dies, those connections drop. L4 has an escape hatch here that L7 doesn't, called direct server return, described under the hood below.
- L4: O(1) work per packet (rewrite addresses, forward). Decision made once per connection. Near wire-speed throughput, minimal added latency, protocol-agnostic.
- L7: parse + route per request, plus TLS decryption for HTTPS. Decision made once per request, so a reused connection pays repeatedly. More CPU per unit of traffic, in exchange for content-based routing.
- Both hold per-connection state so replies route correctly β a memory and failover cost. DSR (below) lets an L4 balancer shed the return-path state.
Go deeperUnder the hood: direct server return (DSR)
Normally traffic flows through the balancer in both directions: client to balancer to backend, then backend to balancer to client. The return path is often the heavy one β a request is small, but the response (a web page, a video chunk) is large. Direct server return is an L4 trick that cuts the balancer out of the return path entirely.
With DSR, the balancer forwards the incoming packet to the backend but leaves the client's address on it as the reply-to. The backend processes the request and sends its response straight back to the client, skipping the balancer. The balancer only ever handles the small inbound half; the large outbound half goes direct. That lets a modest balancer front an enormous amount of outbound traffic, and it's why DSR shows up in high-throughput L4 setups like video and content delivery.
DSR is an L4-only move. It works precisely because the balancer isn't part of the conversation β it just redirects packets. An L7 balancer has terminated the connection and is one endpoint of it, so the response has to come back through it. You can't skip a party that's holding one end of the call.
When to use which
The honest answer is that they're good at different things, and the question 'L4 or L7?' usually answers itself once you name what you need the balancer to do.
- Reach for L4 when the traffic isn't HTTP (a database protocol, a message broker, a game or streaming protocol), when you need the absolute lowest latency and highest throughput, when you must not decrypt the traffic (the private key stays on the backends), or when you want direct server return for heavy outbound volume.
- Reach for L7 when you need to route by content β send /api to one pool and /static to another, split by Host for multi-tenant, canary by header β or when you want the balancer to terminate TLS, retry a failed request on another backend, rewrite headers, or enforce per-request policy like rate limits and web-application-firewall rules.
- The tell: if what you want to route on lives inside the HTTP request, you need L7, because L4 can't see it. If you only need to spread connections and want it fast and protocol-agnostic, L4 is the simpler, cheaper tool.
The two, side by side
Every row below is a consequence of the one difference at the top: whether the balancer reads the request or only the envelope.
| L4 (transport) | L7 (application) | |
|---|---|---|
| Routes on | Source/destination IP and port | URL path, headers, cookies, method |
| Reads the payload | No | Yes |
| Routing granularity | Whole connection | Individual request |
| TLS | Passthrough (backend decrypts) | Terminates (balancer decrypts) |
| Sticky sessions | By connection (natural) | By cookie the balancer sets |
| Health check | Is the port open? (TCP connect) | Does /health return 200? (HTTP) |
| Per-request retry | No β connection is pinned | Yes β can retry on another backend |
| Cost | Near wire speed, protocol-agnostic | More CPU per request, HTTP-only |
| Examples | AWS NLB, IPVS, Maglev | AWS ALB, nginx, HAProxy, Envoy |
Two rows deserve a word because they trip people up. Sticky sessions β keeping one client pinned to one backend, so its in-memory session is there β come for free at L4 because a connection is already pinned; at L7, where each request can go anywhere, the balancer has to actively pin by setting and reading a cookie. And health checks differ in depth the same way everything else does: an L4 balancer can only ask whether the backend's port accepts a TCP connection, while an L7 balancer can send a real HTTP request to /health and require a 200, catching a backend that's listening but broken.
In the wild
The same product lines usually offer both, and the naming tells you which layer you're getting.
- AWS splits them by product: the Network Load Balancer (NLB) is L4 β it handles TCP/UDP at millions of connections per second with ultra-low latency and can hand out static IPs β while the Application Load Balancer (ALB) is L7, doing path- and host-based routing, TLS termination, and per-request features. Teams commonly run NLB in front of ALB.
- HAProxy is one binary that does both, chosen by a mode setting: 'mode tcp' makes it an L4 balancer that forwards connections blind, and 'mode http' makes it an L7 balancer that parses requests and can route on them. The same config file, one line apart, is the whole L4/L7 switch.
- nginx does L7 HTTP load balancing in its http module, and L4 TCP/UDP load balancing in a separate stream module β again, two modes of one tool for the two layers.
- Envoy is an L7 proxy at heart (the data plane of service meshes like Istio), doing rich per-request routing and TLS, though it can also operate as an L4 TCP proxy when that's all that's needed.
- Google's Maglev is a software L4 balancer that fronts Google's services at massive scale, typically ahead of L7 proxies β the canonical 'L4 in front of L7' arrangement.
Common misconceptions & gotchas
Is L7 just strictly better since it can do more?
No β 'more capable' and 'better for the job' aren't the same. L7's per-request parsing and TLS decryption cost CPU, and it can only handle HTTP. If your traffic is a database protocol, or you need the lowest possible latency, or you must not decrypt the traffic, L4 is the right tool precisely because it does less. More features you don't need is just more cost and more surface to break.
Why can't an L4 balancer route by URL?
Because the URL is inside the request, and for HTTPS it's inside the encryption too. An L4 balancer routes on the connection's envelope β source and destination IP and port β and deliberately never opens the payload. It has no way to see 'GET /api/checkout' without becoming an L7 balancer that parses (and, for HTTPS, decrypts) the request.
If an L7 balancer terminates TLS, do the backends get plaintext?
By default the hop from the balancer to the backend is plaintext, since the balancer already decrypted the request. That's usually fine inside a trusted network, but if it isn't, you re-encrypt on that internal hop (sometimes called TLS re-encryption or end-to-end TLS) so the traffic is protected all the way. An L4 passthrough setup sidesteps this entirely because the backend is the only thing that ever decrypts.
The backend sees the balancer's IP, not the client's. How do apps get the real client IP?
At L7 the balancer adds it in a header β the X-Forwarded-For header carries the original client IP so the backend can read it from there. At L4, since there's no HTTP to add a header to, you either preserve the client IP at the network level (DSR and some L4 modes keep the original source IP) or use a protocol like PROXY protocol that prepends the real address. It's a real gotcha: naive setups log every request as coming from the load balancer.
Are sticky sessions an L4 thing or an L7 thing?
Both, but done differently. At L4 stickiness is automatic β a connection is already pinned to one backend, so everything on it stays put. At L7, where each request can be routed independently, the balancer has to create stickiness on purpose, usually by setting a cookie on the first response and routing later requests with that cookie back to the same backend. Either way, the better long-term fix is stateless backends with shared session storage, so stickiness stops mattering.
QuizYour service is HTTPS and you need the load balancer to send /api/* requests to one pool and /images/* to another. A colleague suggests an L4 balancer for its speed. What's the problem?
- None β L4 balancers route by URL path, they're just faster at it.
- An L4 balancer routes on IP and port and never reads the request, and the path is encrypted inside TLS anyway β it literally cannot see /api vs /images. Path-based routing requires an L7 balancer that terminates TLS.
- L4 works, but you'd need to disable TLS first.
- L4 can do it, but only with sticky sessions enabled.
Show answer
An L4 balancer routes on IP and port and never reads the request, and the path is encrypted inside TLS anyway β it literally cannot see /api vs /images. Path-based routing requires an L7 balancer that terminates TLS. β Routing by URL path is content-based routing, and content lives inside the HTTP request. An L4 balancer routes only on the connection's envelope (source/destination IP and port) and never opens the payload β and for HTTPS the path is encrypted, so even opening it would show ciphertext. To route /api and /images to different pools, you need an L7 balancer that terminates TLS and reads the request. L4's speed is real, but it comes from not looking at the very thing you need to route on here.
In an interview
State the fork in one line first: an L4 balancer routes on the connection (IP and port) without reading the payload, and an L7 balancer terminates the connection and routes on the HTTP request. Then let the consequences fall out of that β every capability difference traces back to whether the balancer reads the request.
- Lead with the core difference and derive the rest: because L4 doesn't read the payload, it's fast and protocol-agnostic but can't route by URL, can't retry a single request, and pins a whole connection to one backend. Because L7 reads the request, it can route by path/header/cookie and retry per request, but it costs CPU and must terminate TLS to see inside.
- Name the TLS split cleanly: L7 terminates TLS (holds the key, decrypts, sees plaintext); L4 passes it through (backend decrypts, key never leaves the backend). This is the trap question β a strong answer knows L4 literally can't route by URL over HTTPS because the URL is encrypted.
- Have the operational details ready: sticky sessions (free at L4 by connection, cookie-based at L7), health checks (TCP port-open at L4 vs an HTTP /health 200 at L7), and how the backend recovers the client IP (X-Forwarded-For at L7, PROXY protocol or DSR at L4).
- Show you know they combine: real systems chain an L4 tier (scale, DDoS absorption, e.g. NLB or Maglev) in front of an L7 tier (smart routing, TLS, e.g. ALB or Envoy). Don't present it as a single either/or choice.
- Keep it distinct from the balancing algorithm: round-robin vs least-connections vs power-of-two-choices is a separate axis both layers share β don't conflate 'which layer' with 'which server'.
PredictAn interviewer asks: 'We terminate TLS at our L7 load balancer today. Our security team now says the private key must never live outside the application servers. What changes?' What's the strong answer?
Hint: What does 'terminate TLS' require the balancer to hold, and which layer avoids holding it?
Recognize that the requirement forbids TLS termination at the balancer, because terminating TLS means the balancer holds the private key and decrypts the traffic β exactly what security is ruling out. So the balancer can no longer read the request. The move is to switch that tier to L4 with TLS passthrough: the balancer forwards the encrypted connection straight to a backend, and each application server terminates TLS itself, keeping the key on the backend. The honest cost you name in the same breath is the capability you lose: no more path- or header-based routing, no per-request retries, no header rewrites at the balancer, because it can no longer see inside the request. If the team still needs content-based routing, the answer is to push that logic into the application servers (which now see the decrypted request) or accept the trade. Naming both the mechanism (passthrough keeps the key on the backend) and the cost (you give up L7 features) is what makes the answer strong.
References & further reading
- Cloudflare β Types of load balancers (L4 vs L7) β Cloudflare's plain-English breakdown of routing at the transport vs application layer.
- AWS β Network Load Balancer (L4) β The L4 balancer: TCP/UDP, millions of connections per second, static IPs, ultra-low latency.
- AWS β Application Load Balancer (L7) β The L7 balancer: path/host routing, TLS termination, per-request features.
- HAProxy β Configuration Manual (mode tcp vs mode http) β One binary, both layers: 'mode tcp' is L4 forwarding, 'mode http' is L7 request parsing.
- nginx β TCP and UDP load balancing (the stream module) β nginx's L4 balancing, alongside its L7 HTTP load balancing in the http module.
- Eisenbud et al. β Maglev: A Fast and Reliable Software Network Load Balancer (NSDI 2016) β Google's software L4 balancer, the canonical 'L4 in front of L7' front door.