Skip to content

Load Balancer L4 vs L7#

A load balancer either sees just the TCP/UDP envelope (L4) or also parses the application protocol (L7). The choice drives speed, observability, and routing capability.

flowchart LR
  subgraph L4[L4 - transport layer]
    C1([Client]) --> NLB[NLB / HAProxy TCP] --> B1[Backend]
    NLB -. sees only IP+port .-> Note4[no parse]
  end
  subgraph L7[L7 - application layer]
    C2([Client]) --> ALB[ALB / NGINX / Envoy]
    ALB -. parses HTTP .-> Route[route by Host, Path, header]
    ALB --> B2[Backend by route]
    ALB --> B3[Backend by route]
  end

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    class C1,C2 client;
    class NLB,ALB edge;
    class B1,B2,B3 service;

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

L4 LBs forward at line rate with minimal CPU; L7 LBs add routing flexibility and observability at the cost of CPU and connection handling.

A load balancer's layer determines what it can see, what it can decide, and how fast it can do it.

L4 (transport)#

flowchart TB
  C([Client]) --> LB[L4 LB]
  LB -.hash on 5-tuple .-> P[pick backend]
  P --> B1[Backend 1]
  P --> B2[Backend 2]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

L4 LBs forward TCP segments or UDP datagrams without parsing application data:

  • Cheap: minimal per-packet CPU.
  • Protocol-agnostic: works for HTTP, gRPC, SMTP, MQTT, custom TCP.
  • Limited intelligence: cannot route by URL, terminate TLS (without proxy), or inspect headers.

Scheduling algorithms#

Algorithm Mechanism
Round-robin Next backend in list
Least connections Backend with fewest active TCP connections
Source IP hash hash(client_ip) → backend; sticky by IP
Weighted Backends with explicit weight
Maglev / consistent hash Minimal churn on backend add/remove

Direct Server Return (DSR)#

sequenceDiagram
  participant C as Client
  participant LB as L4 LB
  participant B as Backend
  C->>LB: request (dst=LB)
  LB->>B: forwarded (preserves dst=LB)
  B-->>C: reply directly (src=LB - spoofed)

Backend responds directly to client, bypassing the LB on the return path. Doubles aggregate throughput because LB only sees inbound. Used by Google Maglev and high-throughput CDNs. Requires backend kernel cooperation.

L7 (application)#

flowchart LR
  C([Client]) --> LB[L7 LB]
  LB -.->|Host: api.foo| R1[API fleet]
  LB -.->|Host: static.foo| R2[Static CDN]
  LB -.->|Path: /admin| R3[Admin fleet]
  LB -.->|cookie| SS[sticky backend]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

L7 LBs terminate TCP, parse HTTP (or other protocols), and then make a per-request routing decision:

  • Route by Host header: same IP, many tenants.
  • Route by URL path: /api/v1 vs /api/v2.
  • Route by header / cookie: A/B tests, canary, mobile vs web.
  • Rewrite URLs / headers: redirects, BFFs.
  • Compress, cache, WAF, ratelimit: per-request decisions.

Costs#

  • One TCP connection per client AND one per backend (vs L4's pass-through).
  • Per-request CPU (parsing, possibly TLS handshake).
  • Stateful behind a single LB (connection IDs, request state).

TLS termination#

flowchart LR
  C([HTTPS client]) -->|TLS| LB[L7 LB<br/>terminates TLS]
  LB -->|HTTP or mTLS| B[Backend]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

LBs typically terminate TLS so they can route on HTTP. To keep traffic encrypted internally, re-originate TLS (or use mTLS) from LB to backend.

Passthrough#

When the LB doesn't need to inspect content (e.g. SNI-only routing), it can passthrough TLS:

  • Inspect the TLS ClientHello's SNI extension to pick a backend.
  • Forward bytes unmodified.
  • Backend terminates TLS.

Sticky sessions#

flowchart LR
  C1([Client]) -->|cookie SERVERID=2| LB --> B2[Backend 2]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

L7 LBs implement stickiness via cookies (LB-set or app-set) or hashing. L4 LBs can use source-IP hash. See sticky-sessions.

Combining L4 and L7#

flowchart LR
  C([Client]) --> NLB[L4 NLB<br/>line rate]
  NLB --> ALB[L7 ALB / Envoy]
  ALB --> B1[Backend]
  ALB --> B2[Backend]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;

Production pattern: an L4 LB at the edge for line-rate ingress + DDoS scrubbing; L7 LBs (Envoy / ALB) downstream for routing. AWS's NLB → ALB chain is canonical.

Health checking#

L4: TCP open succeeds → healthy. Coarse; misses app-level issues.

L7: GET /healthz returns 200 → healthy. More accurate, can verify dependencies.

Active vs passive: active probes (every 5s) catch dead backends quickly; passive (failure on real traffic) catches subtle issues.

Where LB layers fit#

flowchart TB
  LL((L4 vs L7<br/>load balancer))
  LB[Load balancer<br/>parent topic]
  PR[Proxy types<br/>reverse proxy = L7 LB]
  SS[Sticky sessions<br/>affinity policies]
  GW[API Gateway<br/>specialised L7]
  LB --> LL
  PR -. same shape .- LL
  LL --> SS
  LL --> GW

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class LL service;
    class LB,PR,SS,GW datastore;

Glossary & fundamentals#

Tag Concept What it is Page
HLD Load balancer parent topic load-balancer
HLD Sticky sessions session affinity policies sticky-sessions
HLD Proxy types reverse proxy = L7 LB proxy-types
HLD API Gateway specialized L7 LB api-gateway

Quick reference#

L4#

  • Sees IP + port only
  • Cheap, line-rate
  • Protocol-agnostic
  • Examples: AWS NLB, HAProxy TCP, IPVS, Maglev

L7#

  • Parses HTTP (or other)
  • Routes by Host, Path, header, cookie
  • TLS termination, compression, WAF
  • Examples: AWS ALB, NGINX, Envoy, Traefik

Scheduling#

Algo Use
Round-robin Default
Least connections Long-lived connections
Source-IP hash Stickiness without cookies
Weighted Canary / rollout
Maglev / consistent hash Min churn on adds

DSR (Direct Server Return)#

Backend replies directly to client, bypassing LB on return. Doubles throughput.

TLS modes#

  • Terminate at LB (decrypt → route)
  • Re-encrypt to backend (mTLS optional)
  • Passthrough (SNI-only routing; backend terminates)

Health checks#

  • L4: TCP open
  • L7: HTTP /healthz 200

Common pattern#

Edge L4 (NLB) → L7 (ALB / Envoy) → backends.

Refs#

  • Eisenbud et al. - Maglev (NSDI 2016)
  • AWS ELB docs
  • Envoy LB overview
  • HAProxy docs

FAQ#

What is the difference between L4 and L7 load balancers?#

L4 balances on IP and port without parsing the payload. L7 parses HTTP, gRPC, or other application protocols and routes by host, path, header, or cookie.

When should I use an L4 load balancer?#

Use L4 for raw TCP or UDP traffic, ultra-low latency, end-to-end TLS pass-through, or non-HTTP protocols like databases, gaming, and VoIP.

When should I use an L7 load balancer?#

Use L7 when you need path or host-based routing, header rewrites, A/B testing, sticky sessions by cookie, WAF integration, or per-request observability.

Is L4 faster than L7?#

Yes. L4 forwards packets without parsing application data, so it has lower CPU cost and latency, but it cannot make routing decisions on HTTP headers or URLs.

Can a load balancer be both L4 and L7?#

Yes. Many modern proxies like Envoy, NGINX, and HAProxy support both modes, and cloud providers offer separate L4 NLB and L7 ALB products.

Further reading#