Load Balancers Explained (2026): L4 vs L7, ALB vs NLB vs GWLB, LCU Costs

Nov 10, 2026 · 12 min · Ajay Kumar

Most load balancer problems I get asked about are not exotic: a health check aimed at a path that redirects to a login page, or an app server closing idle connections after five seconds while the load balancer holds them for sixty. This post explains load balancers from the ground up for 2026: what L4 and L7 actually mean, the algorithms, how health checks and timeouts interact, TLS termination, the AWS ALB vs NLB vs GWLB choice with the GCP and Azure equivalents, how Kubernetes type: LoadBalancer Services get turned into real cloud resources, and how the LCU bill is calculated. If you are already staring at red targets, the companion guide on fixing AWS ALB unhealthy targets and 502/503 errors is the practical half.

What does a load balancer actually do?

A load balancer sits between clients and a pool of backends (AWS calls them targets) and does three jobs: one stable address, a decision about which backend gets each connection or request, and no traffic to backends that look broken. TLS, routing rules, WAF and sticky sessions are features layered on top.

The most useful distinction is not the OSI layer number but whether the load balancer is a proxy or a passthrough. A proxy terminates the client's connection and opens a separate connection to the backend, so there are two TCP connections with two sets of timeouts. A passthrough forwards packets, so the backend sees the client's original source address and replies through the same flow. Google's load balancer selection guide and Microsoft's Azure load balancing overview are both organised around this split.

Proxies can read HTTP, so they route by path and host and generate their own error pages (that is where an ALB's 502 comes from). Passthroughs cannot see inside the stream, so they are faster, preserve client IPs and handle any protocol, but decide only on addresses, ports and flow state.

L4 vs L7 load balancing: what's the difference?

Layer 4 load balancing makes decisions on TCP or UDP: the five-tuple of source IP, source port, destination IP, destination port and protocol. Each new connection or flow goes to a backend and stays there. Layer 7 load balancing parses the application protocol, almost always HTTP/1.1, HTTP/2 or gRPC, and makes a decision per request. With HTTP/2 one client connection can carry hundreds of concurrent requests, so an L7 balancer spreads load properly where an L4 balancer would pin all of them to a single backend.

L7 buffers headers, keeps two connection pools and runs rules; L4 does far less. That is why the Network Load Balancer capacity unit covers 800 new TCP connections a second while the Application Load Balancer's covers 25.

Need host or path routing, edge authentication, WAF or per-request gRPC balancing? L7. Need UDP, static IPs, extreme connection rates or client IPs without X-Forwarded-For? L4. Plenty of architectures run both. I covered the in-cluster proxy options in Nginx vs Caddy vs Traefik.

Load balancing algorithms: round robin, least outstanding requests, weighted random

AWS's ALB offers three per target group, set with the load_balancing.algorithm.type attribute, and the target group attribute docs describe them plainly:

  • Round robin is the default. Requests go to healthy targets in sequence.
  • Least outstanding requests sends each request to the target with the fewest in-flight requests. Better when request cost varies a lot. It cannot be combined with slow start.
  • Weighted random spreads requests randomly and is the only mode that supports Automatic Target Weights anomaly mitigation, which shifts traffic away from a target whose 5xx or connection error rate is well above its peers. AWS says detection needs at least three healthy targets, and that a target can pass every health check yet still be flagged anomalous.

Other balancers add consistent hashing (Envoy's ring hash and Maglev), useful for caches. Sticky sessions sit on top: the algorithm picks the first target, then a cookie pins the client.

How do load balancer health checks work?

A health check is a synthetic request sent to each target on a schedule; pass enough in a row and the target goes in service, fail enough and it comes out. These are the ALB values from the AWS health check documentation, next to the values the AWS Load Balancer Controller annotations apply when it creates the target group for you.

Setting ALB API default AWS LBC Ingress default Allowed range Source
Path / / any URI AWS, LBC
Interval 30 s (instance/ip) 15 s 5 to 300 s same
Timeout 5 s 5 s 2 to 120 s same
Healthy threshold 5 2 2 to 10 same
Unhealthy threshold 2 2 2 to 10 same
Success codes 200 200 200 to 499 same

With raw API defaults a broken target survives two failed checks 30 seconds apart, so up to a minute, and a recovering one needs five passes. The controller's 15-second interval is faster but not instant, which is why Kubernetes deployments behind an ALB need readiness gates (below).

Two behaviours surprise people. The ALB sends health checks with a Host header containing the target's private IP, not your domain, so a virtual-host config that only answers api.example.com will return 404 or a redirect. And if every target in a target group is unhealthy, the ALB fails open and routes to all of them anyway. That is deliberate: a broken health check endpoint should not take the whole site down. You can tune the threshold with the target_group_health.unhealthy_state_routing.minimum_healthy_targets.* attributes.

Keep health checks shallow: a /healthz that checks the database turns one slow database into every target failing at once. Same logic as liveness vs readiness vs startup probes.

TLS termination and the timeout that causes most 502s

A proxy load balancer can terminate TLS, so certificates live in one place (ACM on AWS), optionally re-encrypting to the target. A TCP listener leaves TLS to the backend, which you need when the backend must see the client certificate. An NLB TLS listener terminates at the load balancer but gets a much smaller allowance: 50 new TLS connections a second per NLCU, against 800 for TCP.

Timeouts are where proxies bite. An ALB keeps two connections, client to ALB and ALB to target, and the ALB attribute reference sets idle_timeout.timeout_seconds to 60 by default and client_keep_alive.seconds to 3600. The ALB reuses back-end connections. If your application closes an idle keep-alive connection before the ALB does, the ALB can send a request down a socket the target is closing and get a reset. AWS's troubleshooting page lists that as a 502 cause and tells you to check "whether the keep-alive duration of the target is shorter than the idle timeout value of the load balancer."

Defaults make this common. Node.js sets server.keepAliveTimeout to 5,000 ms. nginx sets keepalive_timeout to 75 s, which is safe behind a 60-second ALB. The rule is simple: the backend's keep-alive must be longer than the load balancer's idle timeout.

L4 has its own version. An NLB tracks TCP flows with a 350-second idle timeout by default, configurable from 60 to 6,000 seconds per listener with tcp.idle_timeout.seconds (TLS listeners stay fixed at 350). Long-lived database or MQTT connections that go quiet for six minutes get silently dropped unless the client sends TCP keepalives.

AWS ALB vs NLB vs GWLB, and the GCP and Azure equivalents

AWS has three current Elastic Load Balancing products plus the legacy Classic Load Balancer. The Gateway Load Balancer is the odd one out: it is not for your application at all, but for inserting firewalls and inspection appliances transparently, encapsulating traffic with the GENEVE protocol on port 6081.

Need AWS Google Cloud Azure Sources
L7 HTTP(S), regional Application Load Balancer Regional external or internal Application LB Application Gateway, Application Gateway for Containers GCP, Azure
L7 HTTP(S), global anycast ALB plus Global Accelerator or CloudFront Global external Application LB Azure Front Door same
L4 TCP proxy NLB (TLS listener terminates) Proxy Network LB Application Gateway TCP/TLS proxy same
L4 passthrough, any IP protocol Network Load Balancer Passthrough Network LB Azure Load Balancer same
Inline appliance insertion Gateway Load Balancer (GENEVE) (none in this list) Gateway Load Balancer (VXLAN) AWS, Azure
DNS-level steering Route 53 routing policies Cloud DNS routing policies Traffic Manager Azure

The table hides one real difference: Google's global Application Load Balancer is a single anycast IP in front of backends in many regions, which AWS only approximates with Global Accelerator. If you are choosing a cloud partly on networking, my EKS vs GKE vs AKS comparison covers how each one wires these into Kubernetes.

How Kubernetes LoadBalancer Services and the AWS Load Balancer Controller work

A type: LoadBalancer Service does nothing until a controller creates a cloud load balancer for it. The Service documentation describes spec.loadBalancerClass, stable since v1.24, which lets you pick a different implementation, and spec.allocateLoadBalancerNodePorts, which you set to false when the load balancer routes straight to pods.

On EKS the usual implementation is the AWS Load Balancer Controller, whose latest release is v3.6.2 (25 October 2026). Version 3.0.0, released in January 2026, made Gateway API support generally available, so the same controller now reconciles Ingress, Service and Gateway/HTTPRoute objects into ALBs and NLBs. That matters if you are moving off ingress-nginx, which I wrote about in Ingress NGINX is retired.

The controller claims a Service when you set loadBalancerClass: service.k8s.aws/nlb, and its install docs note that the chart's service mutator webhook makes it the default for new LoadBalancer Services unless you set enableServiceMutatorWebhook=false. The NLB guide also warns that it provisions an internal NLB by default since v2.2.0. Here is a minimal internet-facing NLB in IP mode:

# Service reconciled by AWS Load Balancer Controller v3.6.2 into an NLB.
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: shop
  annotations:
    # Default scheme is internal; say so explicitly for public traffic.
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    # ip = register pod IPs directly (needs the Amazon VPC CNI); instance = NodePort on each node.
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /healthz
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
spec:
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb   # without this, the in-tree provider may claim it
  allocateLoadBalancerNodePorts: false     # ip targets do not need NodePorts
  selector:
    app: api
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP

Instance vs IP targets matters most. Instance mode sends traffic to a NodePort on every node and kube-proxy forwards it, an extra hop that hides per-pod health. IP mode registers pod IPs directly. IP mode is also the only one that supports the controller's pod readiness gates, which hold a rolling update until the new pod is actually healthy in the target group. Without them a Deployment can outrun the health checks and you get 5xx bursts on every deploy.

What does an AWS load balancer cost? LCUs explained

ELB pricing has two parts: an hourly charge and a capacity unit charge. In US East (N. Virginia), per the ELB pricing page, an ALB is $0.0225 an hour plus $0.008 per LCU-hour, an NLB is $0.0225 an hour plus $0.006 per NLCU-hour, and a GWLB is $0.0125 an hour per Availability Zone plus $0.004 per GLCU-hour. Partial hours bill as full hours, data transfer is extra, and each public IPv4 address the load balancer uses costs $0.005 an hour.

An ALB LCU is the highest of four dimensions, averaged over the hour: 25 new connections a second, 3,000 active connections a minute (1,500 with mutual TLS), 1 GB processed an hour for EC2, container and IP targets (0.4 GB for Lambda), and 1,000 rule evaluations a second, where the first 10 rules are free. You pay only for the largest one.

Take an API doing 100 new connections a second, 6,000 concurrent connections, 5 GB an hour of traffic and 15 listener rules at 50 requests a second. That is 4 LCUs for connections, 2 for active connections, 5 for bytes and 0.25 for rules ((15 minus 10) times 50 is 250 evaluations a second). Bytes win at 5 LCUs.

ALB LCUs for one example API: you pay for the largest dimension only New connections, 100/s 4.0 LCU Active connections, 6,000 2.0 LCU Processed bytes, 5 GB/h 5.0 LCU, billed Rule evaluations, 250/s 0.25 LCU 0 5 LCU ALB, 730 h: $16.43 hourly + 5 LCU x $0.008 x 730 = $29.20, about $45.63 before IPv4 and data transfer NLB, same 5 GB/h over TCP: $16.43 hourly + 5 NLCU x $0.006 x 730 = $21.90, about $38.33
Worked example using US East (N. Virginia) list prices and LCU definitions from the AWS Elastic Load Balancing pricing page; the traffic profile is illustrative, not measured.

Over a 730-hour month that ALB costs $16.43 in hourly charges and $29.20 in LCUs, about $45.63 before IPv4 and data transfer. The same bytes through an NLB TCP listener cost $21.90 in NLCUs. For most services bytes processed is the dimension that bills. Consolidating small ALBs into one with host rules (the controller's group.name annotation does this for Ingresses) usually saves more, because each ALB carries its own hourly charge and IPv4 addresses. I listed more of these traps in why is my AWS bill so high.

FAQ

What is the difference between an ALB and an NLB?

An ALB is a layer 7 HTTP proxy: it reads each request, routes by host, path or header, and terminates TLS. An NLB is a layer 4 balancer for TCP, UDP and TLS that forwards connections, preserves client IPs and handles far higher connection rates.

What is an LCU in AWS?

A Load Balancer Capacity Unit is the billing unit for an ALB. Each hour AWS measures new connections, active connections, processed bytes and rule evaluations, converts each into LCUs, and bills only the largest at $0.008 per LCU-hour in US East.

Should I use target type ip or instance on EKS?

Use ip unless you have a reason not to. It registers pod IPs directly, removes the extra NodePort hop, lets the load balancer see per-pod health, and is required for the AWS Load Balancer Controller's pod readiness gates.

Why does my ALB return 502 Bad Gateway?

The ALB generated the 502 itself because the conversation with the target went wrong: the target reset the connection, sent a malformed response, failed a TLS handshake, or closed a keep-alive connection while a request was in flight. The step-by-step fix guide walks through each cause with commands.

This quarter, if I inherited an AWS estate, I would do three boring things. Turn on ALB access logs everywhere, because they are the only record of whether a 5xx came from the load balancer or the target. Check every backend's keep-alive timeout against its load balancer's idle timeout, because that mismatch causes intermittent 502s that no dashboard explains. And count the ALBs, because a dozen near-idle ones with their own hourly fee and IPv4 addresses are usually cheaper as one with host rules. None of it is clever, and AI traffic still goes through a load balancer someone has to configure correctly.

Related: How to Fix 502 Bad Gateway and 504 Gateway Timeout Errors (2026), AWS VPC Explained (2026): Subnets, NAT Gateways, Endpoints and Costs and Nginx vs Caddy vs Traefik in 2026: HAProxy, Envoy and Which to Pick.

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related