The idea: the layer decides what the balancer can see

A load balancer stands at one public address (the VIP, here 203.0.113.10:443) and spreads the traffic over several backends. The names L4 and L7 come from the OSI layers. An L4 balancer works at the transport layer: it sees IP addresses and TCP ports, nothing more. An L7 balancer works at the application layer: it reads the HTTP request itself. On the canvas, the same connections from Alice and Bob go through both kinds at once, in front of the same three machines, one message per step.

One HTTPS packet drawn as layers: IP header with source 198.51.100.7 and destination 203.0.113.10, TCP header with ports 50001 and 443, then a TLS record holding GET /api/cart, Host and Cookie. The L4 balancer reads only the IP and TCP headers; the L7 proxy, which holds the certificate, decrypts the TLS record and reads the method, path, host and cookie.
An L4 balancer decides from the address and port fields; an L7 proxy decrypts and decides from the request.

L4: forward packets by the 5-tuple

A TCP connection is named by its 5-tuple: source IP, source port, destination IP, destination port, protocol. When a SYN arrives, the L4 balancer hashes the 5-tuple, picks a backend, and writes a row in its connection table. Every later packet of that connection is looked up in the table and goes to the same backend, so the connection never breaks. The balancer does not answer the handshake: the TCP and TLS sessions run end to end between the client and the backend, and TLS stays encrypted on the way through (Demo: one request through each). Linux IPVS, AWS Network Load Balancer, Google Maglev and HAProxy in mode tcp work this way.

It forwards in one of two ways. With NAT it rewrites the destination address to the backend's (DNAT); the backend's replies must come back through the balancer, which rewrites the source back to the VIP. With DSR (direct server return) it rewrites only the destination MAC address; each backend has the VIP on its loopback interface, so it accepts the packet and answers the client directly, from the VIP (Demo: DSR: replies skip the balancer). Replies are usually much bigger than requests, so DSR saves most of the balancer's bandwidth. The price: the backends must be on the same layer-2 network (or reached through a tunnel), and they need the loopback and ARP settings.

Two rows. NAT: client to balancer to app2, and the reply from app2 back to the balancer, then to the client; four hops, both directions through the balancer. DSR: client to balancer to app2, and the reply goes from app2 straight to the client with source 203.0.113.10; the balancer sees only the request direction.
With NAT both directions pass the balancer; with DSR the big direction, the replies, goes around it.

L7: end the connection, route each request

An L7 balancer is a reverse proxy (see forward vs reverse proxy). It accepts the client's TCP connection itself, holds the site's certificate so TLS ends there, and reads each request. Only then does it pick a backend, by rules on the path, the host, a header or a cookie. Here /api/* goes to app3 and everything else to app1 and app2, round robin. It sends the request over a second connection of its own, plain HTTP on the private network, and keeps those connections open in a pool for the next requests. Because the backends see the proxy's address (10.0.0.2), it adds X-Forwarded-For with the client's. HAProxy in mode http, nginx, Envoy, AWS Application Load Balancer and an API gateway work this way.

One long connection, many requests

Browsers keep a connection open and send many requests on it (HTTP/1.1 keep-alive, and with HTTP/2 or gRPC a single connection can carry thousands). An L4 balancer chose once, at the SYN, so every request on that connection lands on the same backend. With a few long connections the load ends up uneven: one gRPC client can pin a whole server. An L7 proxy decides per request, so the same connection spreads over all backends, and each path reaches the pool meant for it (Demo: one connection, three requests: L4 gives 3 / 0 / 0, L7 gives 1 / 1 / 1).

Sticky sessions

Sometimes a user's requests must reach the same backend, for example when the session lives in that server's memory. An L4 balancer can only stick by source IP: it hashes the address alone. Everyone behind one office NAT or mobile carrier gateway then lands on one backend, and a phone that changes network loses its session. An L7 proxy sticks by cookie: on the first reply it adds Set-Cookie: SERVERID=app1, and requests that carry it go back to app1, one user at a time (Demo: sticky sessions behind an office NAT). Rather than either, most apps keep sessions in a shared store so any backend can serve any request.

Health checks

A balancer probes its backends and stops sending to the ones that fail. An L4 check is a TCP connect: if the port accepts, the backend is up. That misses the common failure where the process is running but the app is broken: the database is gone and every request answers 500. An L7 check sends a real request, GET /healthz, and counts only a 200, so the broken backend is taken out (Demo: health checks: app2 returns 500: L4 keeps sending to app2, L7 stops). Real checkers wait for several failures in a row before marking a backend down (HAProxy fall 3) and several successes before bringing it back (rise 2), so one slow answer does not flap the pool. L4 balancers can be given HTTP checks too; what they cannot do is look at the live requests.

Cost, and which one to use

An L4 balancer does almost no work per packet: a table lookup and an address rewrite, often in the kernel or on the network card, so one machine forwards millions of packets per second, and with DSR it never sees the replies. An L7 proxy does a TLS handshake per client connection, decrypts and parses every request, and holds two connections for each one in flight, so it needs far more CPU and memory per request. Large sites use both: an L4 tier at the edge spreads connections over a fleet of L7 proxies, and the proxies route the requests.

L4 balancerL7 balancer
SeesIP addresses and portsthe whole HTTP request
Chooses a backendonce per connectiononce per request
Routes by path or headernoyes
TLSpasses through; the backends hold the certificateends at the balancer
Connectionsone, client to backendtwo: client to proxy, proxy to backend (pooled)
Backends seethe client's IPthe proxy's IP, plus X-Forwarded-For
Sticky sessionsby source IPby cookie
Health checkTCP connectHTTP request, checks the status
Repliesthrough it (NAT) or around it (DSR)always through it
Cost per requestvery lowTLS, parsing, buffering
Other protocolsany TCP or UDPonly the ones it speaks

To spread keys over servers so that adding one moves few of them, a balancer can use consistent hashing instead of hash mod N (Maglev uses its own table for this).

What the page leaves out

The handshakes are drawn as one message each way and the closing FIN packets are not drawn. It leaves out UDP and QUIC, connection draining and slow start for new backends, weighted and least-connections algorithms, keeping the connection table in sync between two balancers, how the VIP itself fails over (VRRP, anycast with ECMP), the PROXY protocol that hands the client address to a backend at the TCP level, TLS passthrough routing by SNI, and HTTP/2 multiplexing to the backends.