Server

ALB vs NLB (and When to Use Each)

By Prabath Thalangama· October 24, 2026· 4 min read
#aws#load-balancing#networking

Introduction

AWS has three current load balancer types: ALB (Application, L7), NLB (Network, L4), and GWLB (Gateway, for inline appliances — out of scope here). For a normal web service the choice is ALB vs NLB, and it comes down to whether you need HTTP-aware routing or raw L4 pass-through.

Feature comparison

ALB (L7) NLB (L4)
OSI layer 7 (HTTP/HTTPS/gRPC/WebSocket) 4 (TCP/UDP/TLS)
Path / host / header routing yes no
Redirects, fixed responses yes no
TLS termination yes yes (TLS listener) — or pass-through TCP
Source client IP to target via X-Forwarded-For header preserved (real IP) with target-type instance/ip
Static IP / Elastic IP no (DNS name, IPs change) yes (one per AZ, can assign EIPs)
Extreme throughput / low latency / millions of connections good better (kernel-level, no per-request processing)
WebSocket / long-lived connections yes yes
Idle timeout configurable (default 60s) 350s for TCP, not configurable for TCP flows
Authentication (OIDC/Cognito) yes no
WAF integration yes no (put a WAF elsewhere)
Slow start, least-outstanding-requests yes no (flow hash)
Health checks HTTP/HTTPS with expected codes/body TCP, HTTP, HTTPS
Pricing per hour + LCU (new conns, active conns, bandwidth, rule evals) per hour + NLCU (similar, generally cheaper for high bandwidth / low request rate)

Choose ALB when

  • It's HTTP(S) and you want path/host routing (one LB, many services / microservices, an ingress).
  • You want TLS termination + ACM, HTTP→HTTPS redirect, HSTS at the edge.
  • You want WAF, OIDC/Cognito auth at the load balancer.
  • gRPC or WebSocket apps that benefit from L7 features.
  • Kubernetes Ingress on EKS (the AWS Load Balancer Controller provisions ALBs from Ingress).

Choose NLB when

  • You need a static IP (firewall allowlisting by the client, or a fixed IP for a partner integration).
  • Non-HTTP protocols — a database proxy, a game server, SMTP, MQTT, DNS (UDP), syslog.
  • Extreme scale — millions of concurrent connections, very high PPS, or latency-critical (NLB adds ~microseconds; ALB adds ~milliseconds).
  • TLS pass-through — the backend must terminate TLS itself (mTLS to the app, or the app needs the raw cert).
  • You want the real client IP with zero application changes (no X-Forwarded-For parsing).
  • Kubernetes Service type: LoadBalancer for a non-HTTP service.

Common patterns

  • ALB behind an NLB — NLB for a static IP + X-Forwarded-For-free client IP, ALB behind it for L7 routing. (AWS supports ALB as an NLB target.)
  • CloudFront → ALB — CDN + edge caching in front, ALB for origin routing.
  • NLB for ingress, per-service ALBs internally — rare; usually one ingress ALB is enough.

Verification and troubleshooting

aws elbv2 describe-load-balancers --query 'LoadBalancers[].{Name:LoadBalancerName,Type:Type,Scheme:Scheme,DNS:DNSName}'
aws elbv2 describe-target-health --target-group-arn ...
aws elbv2 describe-target-groups --query 'TargetGroups[].{Name:TargetGroupName,Proto:Protocol,HC:HealthCheckPath}'
dig +short my-alb-123.eu-west-1.elb.amazonaws.com    # ALB IPs change — always use the DNS name
  • Targets unhealthy on an ALB — health check path returns non-2xx, wrong port, or the target's security group doesn't allow the ALB's SG.
  • NLB targets unhealthy — for target-type: instance, the health check goes to the instance on the target port and the instance SG must allow the NLB's subnet CIDRs (NLB has no security group of its own pre-2023; newer NLBs can have one). For ip target-type + preserve_client_ip, the target SG must allow the client IPs, not the NLB's.
  • App sees the LB's IP, not the client's — ALB: parse X-Forwarded-For (rightmost trusted entry). NLB instance/ip: client IP is preserved at L4; if the app still sees a private IP, preserve_client_ip is off or it's an alb-type target.
  • Connections dropped at ~350s (NLB) — the TCP idle timeout; send keepalives or app-level pings for long-lived connections. ALB's 60s default is even shorter — raise it.
  • 502 from ALB — backend closed the connection, response headers malformed, or keep-alive timeout on the target shorter than the ALB's idle timeout (set the target's keep-alive higher than 60s).
  • Uneven load across AZs (NLB) — cross-zone load balancing is off by default for NLB (and incurs cross-AZ data charges). Enable it if you want even distribution.
  • Static IP "changed" — you used the ALB DNS name and resolved it once. ALB IPs rotate; use the DNS name. NLB IPs are stable (one per AZ).
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.