Skip to main content

NLB vs ALB: TLS Termination, Passthrough, and mTLS

·4 mins

A common misconception #

“An NLB can’t do TLS, only an ALB can” — this is wrong. Both can terminate TLS. The real distinction is what happens after decryption, and it matters most when a requirement calls for mutual TLS (mTLS).

Where TLS sits, loosely speaking #

TLS doesn’t map cleanly onto a single OSI layer — that mapping is a simplification, not a formal standard. The useful mental model: TLS wraps the TCP stream (L4) and hands a decrypted byte stream up to the application. It performs the handshake and encryption/decryption but does not parse HTTP — HTTP (L7) runs on top of it. “HTTPS” is just HTTP-over-TLS-over-TCP.

L7   HTTP    ← application protocol (only ALB reads this)
~L5/6 TLS    ← encryption + handshake (loose analogy — NLB can do this)
L4   TCP     ← NLB's native layer
L3   IP

Because terminating TLS only requires decrypting at the transport boundary — not understanding HTTP — an L4 device like an NLB can terminate TLS too.

ALB vs. NLB-with-TLS-listener vs. NLB-with-TCP-listener #

ALB (HTTPS listener)NLB (TLS listener)NLB (TCP listener)
Terminates TLS?Always — no passthrough mode existsYesNo — passes the encrypted stream through untouched
Decrypts the byte stream?YesYesNo
Parses HTTP (paths, headers, methods)?YesNoNo
Routing decisions based onL7 (host, path, headers)L4 (port, target group)L4 (port, target group)
WAF supportYesNoNo
mTLS (client-cert validation) supportNo (ALB doesn’t support mTLS validation passthrough — termination ends the handshake at the LB)No — AWS explicitly does not support mTLS on an NLB TLS listenerYes — only path to mTLS with an NLB, since the handshake reaches the backend

Both ALB and NLB-with-TLS-listener decrypt the traffic — the difference is what they do with it afterward. ALB reads and routes on the HTTP content underneath; NLB hands off the decrypted TCP stream without ever inspecting it.

Why mTLS forces passthrough #

mTLS requires the backend application itself to receive the client certificate and validate it as part of the initial handshake. If TLS terminates anywhere on a load balancer — ALB or NLB-with-TLS-listener — the handshake ends there, and the application never sees the client cert.

AWS’s NLB documentation is explicit on this: an NLB TLS listener does not support mTLS at all. The only way to get mTLS with an NLB is a TCP listener, where the NLB forwards the raw encrypted bytes untouched and the backend performs the entire TLS handshake — including client-certificate validation — itself.

Key point: the listener port doesn’t decide terminate-vs-passthrough — the listener type does. Port 443 on an NLB behaves completely differently depending on whether it’s configured as a TLS listener (terminates) or a TCP listener (passthrough); the NLB doesn’t even know there’s TLS inside a TCP-listener stream.

Worked example #

Requirement: the application must request a client certificate and validate it as part of the initial handshake (mTLS).

  • ALB — terminates TLS always → handshake ends at the LB → app never sees the client cert → fails.
  • NLB + TLS listener — terminates TLS by choice, and doesn’t support mTLS anyway → fails.
  • NLB + TCP listener — passthrough → handshake reaches the EC2 app → app validates the client cert itself → satisfies the requirement.

Why NLB has no WAF integration #

AWS WAF inspects HTTP-layer content (paths, headers, request bodies), which only an L7 device can provide. Since NLB operates at L4 and never parses HTTP, WAF cannot attach to it directly. Common workarounds: put an NLB in front of an ALB (NLB for static IPs / cross-zone needs, ALB for WAF + L7 routing), or use AWS Shield Advanced for L3/L4 DDoS protection directly on the NLB instead.

Sources: