L4 Load Balancing
L4 load balancing spreads a service's connections across several servers, choosing each backend from the transport-layer header alone: source and destination IP and port, plus protocol. It never reads the payload, so it cannot route by URL, header or cookie, and one connection stays on one server.
Also known as Layer 4 Load Balancing, Layer 3/4 Load Balancing.
Full Explanation
L4 load balancing spreads a service's connections across several servers. It chooses the backend using only the transport-layer header: the source IP, source port, destination IP, destination port and IP protocol number. Google's Maglev paper calls these five fields the 5-tuple. It is not an application-layer balancer. Unlike L7 load balancing, it never reads the URL, a request header or a cookie. F5 draws the boundary exactly: a Layer 4 load balancer "bases the load-balancing decision on the source and destination IP addresses and ports recorded in the packet header, without considering the contents of the packet". It works where TCP and UDP work, so it balances any transport-layer service, not only HTTP. Two consequences follow. The unit of work is the connection, not the request, and every backend behind one address and port must be able to serve anything that arrives there. The name is a shorthand: the decision uses an IP address (Layer 3) as well as a port (Layer 4). So F5 notes that the more accurate term is "Layer 3/4 load balancing". In a CDN, it is the cheap first fan-out inside a point of presence. Every content decision belongs to the TLS-terminating or Layer 7 stage behind it.
How it works
- One advertised address. Clients are given a single service address. So every connection for the service arrives at the same front door. The balancer "distributes network connections from clients who know a single IP address for a service, to a set of servers that actually perform the work" (Linux Virtual Server project).
- The decision comes before the request. TCP "is connection oriented" (RFC 9293, section 2.2). So the connection has to exist before the client can send anything. The balancer therefore picks a server while it still has nothing but headers. That is because "connection must be established between client and server in connection-oriented transport before sending the request content, the load balancer usually selects a server without looking at the content of the request" (Linux Virtual Server project). Content blindness is a consequence of when the decision happens, not a missing feature.
- What it reads. The 5-tuple, and nothing else. Ports are what make that useful. TCP "uses port numbers to identify application services and to multiplex distinct flows between hosts" (RFC 9293, section 2.2). So an address plus a port names one service.
- How it picks. A simple scheduler such as round-robin (A10) is one option. A hash over the flow is another: AWS's layer-4 load balancer "selects a target using a flow hash algorithm based on the protocol, source IP address, source port, destination IP address, destination port, and TCP sequence number" (AWS). At scale the hash is usually a consistent hash paired with a connection tracking table. That way, adding or draining a backend does not move connections that are already established (Maglev).
- How it forwards. Three families exist, and they behave differently. With destination NAT, the balancer changes "the recorded destination IP address from its own to that of the content server it has chosen". Then it rewrites the source on the reply, so the client only ever sees the virtual server (F5). Note the direction: inbound, it is the destination that is rewritten, so the client's own source address survives. With packet forwarding and Direct Server Return, the balancer encapsulates the packet to the chosen endpoint. The server answers the router directly, "so that Maglev does not need to handle returning packets, which are typically larger in size" (Maglev). As a full proxy, it terminates the connection instead. In HAProxy's mode tcp, "a full-duplex connection will be established between clients and servers, and no layer 7 examination will be performed" (HAProxy configuration manual).
- The connection stays put. "Each individual TCP connection is routed to a single target for the life of the connection" (AWS). A UDP flow has the same source and destination throughout. So it "is consistently routed to a single target throughout its lifetime" (AWS).
- Which is why the work is cheap. Nothing above parses a payload and nothing decrypts TLS. A10 notes that messages "are neither inspected nor decrypted". This "allows them to be forwarded quickly, efficiently, and securely".
The transport is not limited to TCP either. The Linux Virtual Server project describes layer-4 balancing as distributing "requests to the servers at transport layer, such as TCP, UDP and SCTP transport protocol". A10 notes that "Layer 4 is also protocol-agnostic". That is why database, LDAP, SMTP and DNS front ends are common L4 workloads. Implementations span a wide range. On the Linux kernel, "IPVS is an implementation of layer-4 load balancing for the Linux kernel" (Linux Virtual Server project). Other implementations include HAProxy in mode tcp and software balancers on commodity servers such as Maglev. At the high end are appliance ADCs, where F5 notes that "the NAT operations might be performed by specialized chips rather than in software".
Why it matters for a CDN
A point of presence answers on a handful of shared service addresses. It has to fan those arrivals out over the edge servers behind them. Anycast gets a client to the right PoP. A layer-4 tier is what spreads the connections once they land. It is the cheapest stage that can do it, since the decision costs a hash over five header fields.
The blindness is the design constraint. The transport header carries no hostname. So the fan-out cannot separate two sites that share an address and port: not by Host header, not by URI, and not by the SNI. The SNI travels in the TLS ClientHello, so it is connection payload rather than header. Every server behind one address and port must be able to serve every site and every path that resolves there. Which site, which cache pool and which origin are decisions for the L7 or TLS-terminating stage behind it.
Multiplexing sharpens this. An HTTP/2 connection "can contain multiple concurrently open streams" (RFC 9113, section 5). Every one of those streams is pinned to whichever backend the connection landed on. So per-request spreading simply does not exist at this layer. That is why edge L4 pools are sized by concurrent connections rather than by request rate. HTTP/3 over QUIC breaks the 5-tuple assumption outright, because a QUIC connection survives a change of client address. AWS routes QUIC "using the Server ID specified in the Connection ID (CID)" and keeps traffic for that CID on one target. It falls back to a flow hash only for initial packets that carry no Server ID.
What CDNs do
- Push the first split into the routers. Each balancer machine announces the service address over BGP. So the router can spread packets across all of them: "When the router receives a VIP packet, it forwards the packet to one of the Maglev machines in the cluster through ECMP, since all Maglev machines announce the VIP with the same cost" (Maglev). Capacity then grows by adding machines rather than by buying a bigger box.
- Keep the return path off the balancer. With Direct Server Return, only the small inbound packets cross the balancer. The responses, which are the bulk of CDN bytes, go straight from the edge server to the router instead (Maglev).
- Process packets outside the kernel. Maglev needs no TCP/IP stack of its own. Introducing a kernel bypass improved per-machine throughput "by more than a factor of five" (Maglev).
- Protect connections across churn. The property to preserve is connection persistence: "packets belonging to the same connection should always be directed to the same service endpoint" (Maglev). A consistent hash plus connection tracking keeps that true while backends are added, drained or lost. That is the difference between a rolling restart and a wave of resets.
- Carry the client address forward explicitly. Where the forwarding mode replaces the source address, the PROXY protocol restores it. It "informs the other end about the layer 3/4 addresses of the incoming connection, so that it can know the client's address" (HAProxy configuration manual). AWS Network Load Balancer implements version 2 of it for the same purpose.
- Peek at the handshake when hostname steering is needed without decrypting. The nginx stream ssl_preread module "allows extracting information from the ClientHello message without terminating SSL/TLS, for example, the server name requested through SNI or protocols advertised in ALPN". HAProxy exposes the same thing in mode tcp as req.ssl_sni. Name it precisely: that is inspection of the TLS handshake, above the transport header. So it is no longer a pure L4 decision, even though it is often sold as one.
Watch out for
- No content awareness. A10 states that layer 4 load balancing "is unable to make decisions based on content". So it cannot "route traffic based on media type, localization rules, or other criteria beyond simple algorithms such as round-robin routing". Anything keyed on the URI, a header or a cookie needs L7.
- Liveness is not health. The transport gives you nothing here: "TCP is connection oriented, though it does not inherently include a liveness detection capability" (RFC 9293, section 2.2). A bare port connect proves a socket is listening. It proves nothing about the application behind it. Most L4 products can do better, and you should make them. For AWS's layer-4 load balancer, the health check protocols "are HTTP, HTTPS, and TCP. The default is the TCP protocol" (AWS). The default is the weak one.
- Know the forwarding mode before you trust the client address. Destination NAT and Direct Server Return leave the client's source address intact. Source NAT and full-proxy modes replace it with the balancer's own. AWS states it plainly for its own product: "when you disable client IP preservation, the source IP address is the private IP address of the Network Load Balancer" (AWS). Source NAT also costs ports. With preservation off, AWS documents support for "about 55,000 connections per minute for each combination of Network Load Balancer IP address and unique target". After that, port allocation errors become likely (AWS). The figure is product-specific. The exhaustion failure mode is general.
- Stickiness at L4 is coarse. The only persistence available is source-address affinity. For AWS's layer-4 load balancer, "The possible value is source_ip". Source-address affinity skews badly, because "all clients behind the same NAT device have the same source IP address. Therefore, all traffic from these clients is routed to the same target" (AWS). One carrier or corporate NAT can collapse a whole population onto a single machine. Cookie-based persistence is an L7 feature.
- One connection can outlast the balancing decision. Keep-alive, WebSocket, HTTP/2 and HTTP/3 all pile many requests onto one connection. All of them stay on one backend until it closes. Adding a backend does not move existing load onto it. Even load in connections can be very uneven load in work.
- IP fragments have no ports. A non-first fragment carries the L3 header only. So a 5-tuple hash cannot classify it: "when Maglev receives a non-first fragment, it cannot make the correct forwarding decision based only on that packet's headers" (Maglev). Balancers need explicit fragment handling to keep the pieces of one datagram together. This is a real source of hard-to-see failures on UDP services.
- Speed is no longer the reason to choose it. F5 records that on current hardware "the performance advantage for Layer 4 load balancing has become negligible or irrelevant in most situations". Choose L4 for non-HTTP transports, for strict end-to-end encryption, or for packet-rate scale. Do not choose it just to save CPU on ordinary HTTP.
Best practice
- Put L4 only in front of an interchangeable pool. If any backend cannot serve any request arriving on that address and port, the tier is in the wrong place.
- Layer it deliberately. Use cheap connection fan-out at L4. Make every content decision at the L7 or TLS-terminating stage behind it.
- Prefer a consistent hash with connection tracking over a plain modulo hash. That way backend churn does not reset live connections.
- Configure an application-level health check rather than accepting the default port check. Let the transport check be the fallback, not the only signal.
- Decide explicitly how the client address reaches the origin: preserved by destination NAT or DSR, or carried by the PROXY protocol. Budget source ports if you source-NAT. This matters because logging, rate limiting and geo decisions all rest on the client address.
- Avoid source-IP stickiness for content delivery. Keep edge backends stateless, so any connection can land anywhere.
- Assume long-lived and multiplexed connections. Size backends for uneven per-connection load. Rebalance by shedding connections rather than expecting the balancer to move requests.
- Route QUIC on the connection ID where the product supports it. That way connection migration does not reshuffle a live session onto a different target.
Examples
# HAProxy L4 mode
frontend tcp_front
bind *:443
mode tcp
default_backend cdn_edges
backend cdn_edges
mode tcp
balance roundrobin
server edge1 10.0.1.1:443 check
server edge2 10.0.1.2:443 check
server edge3 10.0.1.3:443 check
# Linux IPVS (kernel-level L4 LB)
$ ipvsadm -A -t 203.0.113.1:443 -s rr
$ ipvsadm -a -t 203.0.113.1:443 -r 10.0.1.1:443 -m
$ ipvsadm -a -t 203.0.113.1:443 -r 10.0.1.2:443 -m
Frequently Asked Questions
L4 load balancing spreads a service's connections across several servers, choosing each backend from the transport-layer header alone: source and destination IP and port, plus protocol. It never reads the payload, so it cannot route by URL, header or cookie, and one connection stays on one server.
# HAProxy L4 mode
frontend tcp_front
bind *:443
mode tcp
default_backend cdn_edges
backend cdn_edges
mode tcp
balance roundrobin
server edge1 10.0.1.1:443 check
server edge2 10.0.1.2:443 check
server edge3 10.0.1.3:443 check
# Linux IPVS (kernel-level L4 LB)
$ ipvsadm -A -t 203.0.113.1:443 -s rr
$ ipvsadm -a -t 203.0.113.1:443 -r 10.0.1.1:443 -m
$ ipvsadm -a -t 203.0.113.1:443 -r 10.0.1.2:443 -m
Yes. L4 Load Balancing is also known as Layer 4 Load Balancing, Layer 3/4 Load Balancing. L4 load balancing spreads a service's connections across several servers, choosing each backend from the transport-layer header alone: source and destination IP and port, plus protocol. It never reads the payload, so it cannot route by URL, header or cookie, and one connection stays on one server.
Related CDN concepts include:
- Anycast — Anycast announces one IP address from many locations at once; the routing system, usually BGP, …
- L7 Load Balancing — Load balancing at layer 7 of the OSI model, the application layer: the balancer parses …
- TCP (TCP) — TCP is the connection-oriented transport specified in RFC 9293: it delivers application data as one …
- UDP (UDP) — User Datagram Protocol: a connectionless, best-effort transport that puts each message in one IP packet …