Elastic Load Balancing: The Architect’s Deep Dive Into How ALB, NLB, GWLB & CLB Really Work

Elastic Load Balancing: The Architect's Deep Dive Into How ALB, NLB, GWLB & CLB Really Work

Beyond "it spreads traffic across servers" — what happens inside AWS's load balancer fleet on every single connection, and why picking the wrong one quietly caps your system's ceiling.

Most engineers meet Elastic Load Balancing as a single icon on an architecture diagram — a box that says “load balancer,” sitting between users and servers, presumed to just work. That framing hides a genuinely deep piece of distributed systems engineering. Under one product name, AWS actually operates four architecturally distinct load balancing engines — Application Load Balancer, Network Load Balancer, Gateway Load Balancer, and the older Classic Load Balancer — each solving a different problem at a different layer of the network stack, each with its own scaling model, its own failure behavior, and its own set of ways to silently bottleneck a system that looks fine on paper.

This isn’t an introduction to what a load balancer is or why you’d want one — you already know that. This is the conversation you’d have with a principal engineer the week before an ELB decision goes into a design review: what actually happens to a TCP connection from the moment it hits AWS’s edge, why NLB can push tens of millions of connections while ALB tops out far lower per node, and which configuration choices quietly turn a “highly available” load balancer into a single point of failure.

1Advanced Core Concepts — Four Engines, Not One Product

This section assumes you already know what a load balancer does at a basic level. We’re going straight into what separates the four ELB engines architecturally.

Layer 7 versus Layer 4 versus Layer 3/4 packet-forwarding — a real distinction, not marketing

Application Load Balancer (ALB) operates at Layer 7 (the application layer): it terminates every client connection itself, reads the actual HTTP request — the path, the headers, the host — and then opens a brand-new connection to whichever backend it chooses. Network Load Balancer (NLB) operates at Layer 4 (the transport layer): it never terminates the connection at the application level at all; it forwards packets while preserving the original source IP, using a technique that lets it push extreme volumes of connections with very low, consistent latency. Gateway Load Balancer (GWLB) operates even lower, at Layer 3/4, and solves a completely different problem — transparently inserting third-party network appliances (firewalls, intrusion detection systems) into a traffic path using the GENEVE encapsulation protocol, without the appliances or the original traffic needing to know an extra hop exists.

The practical consequence of this layering difference shows up the moment something goes wrong: debugging an ALB issue means thinking in terms of HTTP requests, headers, and status codes, while debugging an NLB issue means thinking in terms of TCP flows, packet loss, and connection resets — two entirely different diagnostic vocabularies for what looks, from an architecture diagram, like the same kind of box.

Analogy

Think of ALB as a receptionist who opens every envelope, reads the letter inside, and decides which department to hand-deliver it to based on its content. NLB is a mail-sorting machine that reads only the address on the envelope and flings it toward the right chute at extreme speed, never opening it. GWLB is a customs inspection checkpoint that every truck passes through on its way somewhere else — the truck doesn’t know the checkpoint exists as a separate stop, it just experiences a brief, transparent detour.

Target groups: the abstraction that decouples “who receives traffic” from “how traffic is chosen”

Every modern ELB type routes traffic to target groups rather than directly to servers. A target group is a named collection of registered targets — EC2 instances, IP addresses, Lambda functions (ALB only), or even other load balancers (GWLB) — plus its own independent health-check configuration, routing algorithm, and deregistration behavior. This decoupling is what makes advanced patterns like weighted traffic shifting between two target groups (for blue/green deployment) or path-based routing across a dozen microservices possible without touching the load balancer’s core configuration at all — the listener rules simply point at different target groups.

Listener rules as a routing engine, not a static forward

On ALB specifically, listener rules form an evaluated, priority-ordered rule set — each rule matches on conditions (host header, path pattern, HTTP method, query string, source IP) and executes an action (forward, redirect, return a fixed response, or authenticate via Cognito or OIDC). This turns the load balancer into a lightweight request router sitting in front of your services, capable of decisions that used to require a dedicated API gateway or reverse proxy layer.

4
DISTINCT ELB ENGINES: ALB, NLB, GWLB, CLB
L3-L7
SPANS THE NETWORK STACK DEPENDING ON ENGINE
1:N
ONE LOAD BALANCER, MANY TARGET GROUPS

Cross-zone load balancing: a default that quietly changes with the engine

Cross-zone load balancing decides whether a load balancer node in one Availability Zone can send traffic to targets registered in a different AZ, or only to targets in its own AZ. ALB has this enabled by default and effectively cannot be turned off in practice, distributing evenly across all healthy targets regardless of AZ. NLB has it disabled by default — each NLB node only forwards to targets in its own AZ unless explicitly enabled — which is a deliberate design choice tied to preserving the extreme performance characteristics NLB is built for, and it is a setting advanced teams check explicitly rather than assume.

!
Advanced Trap

An uneven number of targets per AZ combined with cross-zone load balancing disabled on NLB creates a silent hot spot: an AZ with fewer targets receives the same share of traffic from its local NLB node as an AZ with many targets, so each of those few targets absorbs a disproportionate load. This looks like a mysterious per-instance performance problem until someone checks the per-AZ target count.

Routing algorithms: round robin isn’t the only option, and it isn’t always the right one

ALB target groups support round robin (the default, cycling evenly through healthy targets) and least outstanding requests (routing each new request to whichever target currently has the fewest requests still in flight). The second algorithm matters enormously the moment backend response times vary — a fleet with a mix of fast and slow-responding targets under round robin keeps sending equal shares of traffic to the slow ones, building up a queue there, while least outstanding requests naturally steers new traffic away from targets that are already backed up. NLB, by contrast, uses flow hashing rather than a request-level algorithm at all, since it never sees individual requests, only connections.

Sticky sessions: a feature that trades scalability for statefulness

Both ALB and CLB support session stickiness — pinning a client to the same target for the duration of a session, using either a load-balancer-generated cookie or an application-generated one. This exists to support applications that keep session state in local memory rather than in a shared store, but it directly works against even load distribution: a target that happens to be “sticky” for a disproportionate number of long-lived sessions accumulates load unevenly, and it also means a target’s failure or deregistration invalidates every session pinned to it. Advanced designs treat stickiness as a compatibility bridge for stateful legacy applications, not a default to reach for, preferring externalized session state (a shared cache or database) whenever the application can be changed.

Weighted target groups: the mechanism behind safe traffic shifting

ALB listener rules can forward to multiple target groups simultaneously with configurable relative weights, letting a fixed percentage of traffic go to a new version of a service while the rest continues to the stable version — the core mechanism behind blue/green and canary deployment patterns without any custom traffic-splitting code in the application itself. Because weights are just a listener-rule configuration, shifting from 5% to 50% to 100% traffic on a new version is a configuration change, not a redeploy.

2Internal Working — What Happens On Every Connection

ALB’s connection-splicing model

When a client connects to an ALB, the ALB node fully terminates that TCP (and TLS, if configured) connection — completing the handshake, decrypting the request if HTTPS, and holding it in memory as an actual HTTP transaction. Only after evaluating listener rules does the ALB open a second, independent connection to the chosen backend target and relay the request over it. This is why ALB can rewrite headers, inject its own headers (like X-Forwarded-For to preserve the original client IP, since the backend now sees the ALB’s IP as the connection source), and terminate TLS with its own certificate independent of whatever the backend expects.

NLB’s flow-hashing and near-transparent forwarding

NLB does not terminate connections the way ALB does. It uses a flow hash — computed from source IP, source port, destination IP, destination port, and protocol — to consistently map a given connection to a specific target for the connection’s entire lifetime, then forwards packets toward that target largely unmodified, preserving the original client source IP end to end (a property ALB cannot offer without extra configuration, since ALB itself becomes the visible source IP to the backend). This flow-hash consistency, combined with NLB running on a fleet of nodes built for extremely high packet-per-second throughput, is the core mechanism behind NLB’s ability to sustain massive connection volumes with very low added latency — often just microseconds of overhead versus a direct connection.

graph LR
  Client[Client] -->|TCP SYN| NLBNode[NLB Node]
  NLBNode -->|flow hash lookup| Decision{Consistent Target Assignment}
  Decision -->|same flow, always same target| TargetA[Target Instance A]
  Client2[Client Retransmit] --> NLBNode
  NLBNode -.source IP preserved.-> TargetA
    

Fig 2.1 — NLB’s flow hash pins a connection to one target for its full lifetime and preserves the client’s real source IP end to end, unlike ALB’s connection-splicing model.

GWLB’s GENEVE tunnel and the “bump in the wire” pattern

GWLB works by encapsulating original packets inside a GENEVE tunnel and forwarding them to a fleet of third-party virtual appliances registered as its targets. Those appliances inspect, filter, or transform the traffic, then send it back through the tunnel to GWLB, which forwards it on to its real destination. The elegance of this design is that from the perspective of the original client and server, nothing about the packet path looks different — the appliance is inserted transparently, and GWLB itself handles scaling and health-checking the appliance fleet the same way any other load balancer scales targets, which is why this pattern displaced a generation of manually-scripted “traffic mirroring to a firewall cluster” architectures.

Classic Load Balancer: the legacy model, kept for context

CLB predates target groups entirely — instances register directly with the load balancer, health checks and routing logic are far less flexible, and it operates in a hybrid mode that can do basic Layer 7 request routing or Layer 4 TCP forwarding but not both with the sophistication of ALB or NLB. It persists mainly in older accounts that haven’t migrated; AWS explicitly steers new designs toward ALB or NLB, and understanding CLB’s limitations is mostly valuable for recognizing legacy architecture during a migration assessment rather than for new designs.

ALB

Terminate & re-originate

Full HTTP awareness; backend sees ALB’s IP unless X-Forwarded-For is read explicitly.

NLB

Flow-hash forward

Packets pass through with minimal modification; backend sees the real client IP.

GWLB

GENEVE tunnel insertion

Transparently redirects traffic through third-party appliances and back.

CLB

Legacy hybrid model

Pre-target-group design; retained for backward compatibility, not new builds.

Connection reuse and why keep-alive changes ALB’s effective capacity

ALB reuses backend connections across multiple client requests where HTTP keep-alive allows it, rather than opening a brand-new backend connection per request. A backend that closes connections aggressively (a short keep-alive timeout) forces ALB to re-establish backend connections far more often, adding overhead that shows up as elevated target response time even though the backend’s actual processing logic hasn’t changed at all — an advanced tuning detail that’s frequently mistaken for an application performance regression when it’s actually a connection-reuse mismatch between the ALB and the backend’s own web server configuration.

NLB’s Elastic Network Interface model

Each NLB node is backed by an actual Elastic Network Interface (ENI) in the subnet you place it in, per AZ, which is precisely why NLB — unlike ALB — can be assigned a static private IP or an Elastic IP per AZ: the IP is attached to a real network interface with a stable identity, rather than being an abstraction the load balancer’s shared fleet manages behind DNS the way ALB’s IPs are. This ENI-level presence is also what makes NLB directly compatible with AWS PrivateLink, since PrivateLink’s underlying mechanism is built around exposing a service through exactly this kind of network-interface-level endpoint.

GWLB’s appliance health checking and fail-open versus fail-closed behavior

GWLB continuously health-checks the appliance instances registered as its targets exactly like any other load balancer health-checks targets, but the consequence of an unhealthy appliance is a security-relevant decision every deployment has to make explicitly: does traffic fail open (bypass the now-unavailable appliance and continue toward its destination, prioritizing availability) or fail closed (block traffic entirely until a healthy appliance is available, prioritizing security)? This isn’t a default GWLB imposes — it’s a routing and appliance-configuration decision the operator makes, and getting it wrong in either direction is a genuine production and security incident waiting to happen.

3Data Flow & Lifecycle — From Client Request to Backend Response

The request lifecycle on ALB

A request arrives at one of ALB’s per-AZ nodes (each AZ you enable gets its own set of ALB nodes behind the same DNS name), completes a TCP/TLS handshake, gets parsed as HTTP, is evaluated against the listener’s rule priority list top to bottom until a match is found, is load-balanced to a specific target within the matched target group’s algorithm (round robin by default, or least outstanding requests), gets a new connection opened to that target, and the response is relayed back along the same path, with ALB able to inject response headers, run health-driven retries, or apply sticky-session cookies along the way.

Connection draining and deregistration: the lifecycle event most designs get wrong

When a target is deregistered — during a deployment, an Auto Scaling scale-in event, or a manual removal — the load balancer doesn’t cut it off instantly. Deregistration delay (still commonly called connection draining) keeps the target receiving no new connections while letting its in-flight requests finish for a configurable window before it’s fully removed. Setting this window too short truncates legitimate in-flight requests during every deployment; setting it too long slows down scale-in events and rolling deployments. This single setting is disproportionately responsible for “random 502s during deploys” tickets when misconfigured.

1

Deregistration triggered

Auto Scaling, a deployment tool, or an operator marks a target for removal.

2

New traffic stops

The load balancer immediately stops routing new connections to this target.

3

Draining window

Existing in-flight requests are allowed to complete, up to the configured deregistration delay.

4

Hard removal

Once the window expires, remaining connections are forcibly closed and the target is fully removed.

Health checks as a continuous lifecycle process, not a one-time gate

Health checks run continuously against every registered target on a configurable interval, with separate thresholds for how many consecutive failures mark a target unhealthy and how many consecutive successes mark it healthy again. This asymmetry is deliberate — a target should be pulled from rotation quickly on failure but only re-admitted after proving stability, avoiding a flapping target being cycled in and out of rotation on every borderline health check response.

Slow start: the lifecycle stage between “healthy” and “fully loaded”

ALB target groups support a slow start window — a period after a target first becomes healthy during which it receives a linearly ramping share of traffic rather than its full proportional share immediately. This matters for applications with a real warm-up cost (JIT compilation, connection pool establishment, cache population) — without slow start, a freshly-registered target that’s technically passing health checks but hasn’t warmed up yet gets slammed with a full share of traffic the moment it’s marked healthy, often causing exactly the kind of latency spike or error burst the health check was supposed to prevent.

The lifecycle of a request during an Auto Scaling scale-out event

When Auto Scaling launches a new instance in response to load, that instance registers with the target group, begins failing health checks (because the application hasn’t started serving yet), continues failing until the application is actually ready and passes the configured healthy threshold, and only then starts receiving traffic — optionally ramped via slow start. This sequence means the actual time from “scale-out triggered” to “meaningfully absorbing load” is the sum of instance launch time, application startup time, and health-check confirmation time — a chain advanced capacity planning accounts for explicitly rather than assuming scale-out solves a load spike the moment it’s triggered.

4Advantages, Disadvantages & Trade-offs

Where ELB Wins

  • Native, automatic scaling of the load balancer’s own capacity — you never provision “load balancer instances” or plan their capacity the way you would application servers.
  • Deep AWS integration: ALB understands Auto Scaling groups, ECS/EKS service registration, and Lambda targets natively, removing custom service-discovery glue code.
  • NLB’s packet-forwarding design delivers extreme throughput and consistently low, predictable latency for protocols that don’t need HTTP-level intelligence.
  • GWLB turns “insert a security appliance transparently into every packet’s path” from a bespoke networking project into a managed, horizontally-scaling primitive.

Where ELB Costs You

  • Four engines means the “right” choice depends on protocol needs and traffic shape — picking ALB for a workload that actually needed NLB’s raw throughput (or vice versa) is a common, costly re-architecture.
  • ALB’s connection-splicing model means the backend never sees the original client IP without deliberately reading X-Forwarded-For, a detail that breaks IP-based access logic ported from on-premises designs.
  • NLB’s default AZ-local routing (cross-zone disabled) can create AZ-level hot spots if target distribution across AZs isn’t kept even.
  • Deregistration delay is a trade-off with no free setting — too short truncates requests, too long slows every scale-in and deployment event.
“Choosing an ELB type isn’t a checkbox — it’s deciding which layer of the network stack you want AWS’s opinion baked into your traffic.”

The observability trade-off between engines

ALB’s Layer-7 awareness gives you rich, request-level visibility — path, host, status code, latency split — practically for free. NLB’s Layer-4 design means it fundamentally cannot see or log that kind of application-level detail, because it never parses the request in the first place; its flow logs describe connections, not HTTP semantics. Teams choosing NLB purely for its throughput characteristics need to accept that request-level observability has to be built inside the application itself, since the load balancer layer structurally cannot provide it.

Cost is a trade-off dimension tied to architecture, not just usage volume

ELB pricing combines an hourly charge with a usage-based dimension (Load Balancer Capacity Units for ALB and NLB, which factor in new connections, active connections, processed bytes, and — for ALB — rule evaluations). A design with many complex listener rules and high connection churn accrues cost differently than a design with few rules and long-lived connections, even at identical raw traffic volume. Advanced cost modeling looks at which LCU dimension actually dominates for a given workload rather than assuming cost scales linearly with total bytes transferred. A workload with thousands of short-lived connections and simple routing can cost more than a workload moving far more total data through fewer, longer-lived connections — the bill follows the busiest dimension, not the most intuitive one.

5Performance & Scalability

ALB’s request-per-second ceiling is a function of backend latency, not just connections

Because ALB fully processes each HTTP transaction, its effective throughput is tied to how quickly backends respond and how efficiently connections are reused (keep-alive). A backend with slow response times ties up ALB’s connection pool longer per request, reducing effective request throughput even though ALB itself scales its own node fleet automatically behind the scenes in response to sustained load — a scaling response that itself takes time, which is why sudden, sharp traffic spikes (rather than gradual ramps) are the scenario advanced teams specifically pre-warm ALB for via AWS support requests before a known traffic event.

NLB’s near-linear scaling with almost no per-connection overhead

NLB’s packet-forwarding design means its scaling model looks fundamentally different: because it isn’t parsing HTTP or holding application-layer state, a single NLB can sustain millions of concurrent connections and extremely high packets-per-second, scaling its own capacity in response to demand with a much flatter overhead curve than ALB. This is precisely why NLB is the standard choice for workloads like high-frequency trading gateways, IoT ingestion at extreme connection counts, or any TCP/UDP protocol that doesn’t need HTTP semantics at all.

Analogy

ALB’s throughput is like a translator at a conference — capacity depends on how long each conversation takes, because the translator is actively engaged in every exchange. NLB’s throughput is like a traffic cop directing cars at an intersection — the cop never gets in the car, so throughput depends almost entirely on how many lanes exist, not on how long any individual driver’s trip takes.

Pre-warming and the myth of “load balancers scale infinitely, instantly”

ALB and CLB scale their node fleet in response to sustained traffic patterns, not instantaneously to an unannounced spike ten times normal volume. NLB, due to its design, handles sudden spikes far more gracefully by default. This distinction matters enormously for planned high-traffic events (product launches, ticket sales, live broadcasts) — advanced teams either request AWS pre-warming for ALB ahead of a known spike or choose NLB specifically because its scaling characteristics tolerate sudden bursts without advance notice.

EngineScaling ModelBest Suited ForWeak Point
ALBGradual, traffic-pattern-driven node scalingHTTP/HTTPS microservices, path-based routingSudden unannounced traffic spikes
NLBNear-instant, flow-hash based, very high ceilingExtreme connection volume, low-latency TCP/UDPNo HTTP-level routing intelligence
GWLBScales the appliance fleet behind itTransparent security/inspection insertionAdds a network hop’s worth of latency
CLBLegacy scaling model, less predictableBackward compatibility onlyLacks target-group flexibility entirely

Idle timeout: a quiet performance lever most designs never touch

ALB’s idle timeout (default 60 seconds) determines how long a connection can sit without data before ALB closes it. Applications that hold connections open for long-polling, large file uploads, or WebSocket-style streaming need this raised explicitly; left at the default, ALB terminates a connection mid-transfer, which surfaces as sporadic, hard-to-reproduce failures on exactly the requests that take longest — often the ones that matter most, like large report generation or file uploads.

Target group algorithm choice under real backend variance

Least outstanding requests measurably outperforms round robin the moment backend response times vary by workload type rather than being uniform — for example, a service handling both cheap read requests and expensive write requests on the same target group. Round robin sends both types evenly regardless of target load, while least outstanding requests naturally avoids piling expensive-request backlog onto targets that are already busy. Advanced designs measure backend response-time variance before defaulting to round robin out of habit.

6High Availability & Reliability

Multi-AZ by architecture, not by opt-in feature

Every ELB type is inherently a multi-AZ construct: you enable it across two or more Availability Zones, and AWS runs independent load balancer nodes in each one behind a single DNS name that resolves to multiple IP addresses (one set per AZ). If an entire AZ fails, DNS resolution and health checking naturally route traffic to the nodes in the surviving AZs — there’s no manual failover procedure to trigger, because there was never a single active node to fail over from in the first place.

DNS-based failover means client-side DNS caching matters

Because ELB’s high availability model relies on DNS resolving to multiple, healthy IP addresses, a client or intermediate resolver that aggressively caches a single resolved IP beyond the record’s TTL can keep sending traffic toward an AZ’s nodes even after AWS has already routed around a failure at the DNS level. This is why AWS explicitly recommends against hardcoding an ELB’s resolved IP address anywhere — the DNS name is the actual stable interface, and the IPs behind it are expected to change as AWS manages its own fleet and as failures occur.

Architect’s Note

Treat the ELB’s DNS name as the only stable contract. Any monitoring, firewall rule, or client configuration that pins to a specific resolved IP address has silently reintroduced a single point of failure into a system that was designed specifically to not have one.

Health checks as the reliability boundary between the load balancer and your fleet

ELB’s own high availability protects against load-balancer-node or AZ failure. It does nothing to protect against an unhealthy backend fleet — that boundary is entirely defined by your health check configuration. A health check pointed at a trivial static endpoint that always returns 200 regardless of the application’s actual ability to serve traffic gives you a load balancer that faithfully, reliably routes traffic to backends that can’t actually handle it — a reliability gap that looks like an ELB problem but is actually a health-check design problem.

ADR-021Anti-Pattern
Anti-Pattern

Using a shallow health check endpoint (e.g., a static “OK” page) that doesn’t reflect the application’s actual downstream dependency health.

Why It Fails

The load balancer will keep routing live traffic to a target whose database connection, cache, or upstream API is down, because the shallow check has no way to detect it — the target technically responds, it just can’t do useful work.

Correct Pattern

Implement a health check endpoint that verifies the minimum set of critical dependencies the request path actually needs, so a target reporting healthy is a meaningful, load-bearing signal rather than a formality.

ELB is a regional resource — multi-region resilience is a separate design layer

An ELB, no matter which type, is scoped to a single AWS Region and its Availability Zones — it does not span regions. High availability against an entire region-level event requires a layer above ELB entirely, typically Route 53 with health-checked failover or latency-based routing across independently deployed ELB stacks in two or more regions. Teams sometimes conflate “our load balancer is Multi-AZ” with “our system is resilient to any AWS-level failure,” which conflates two genuinely different resilience boundaries — AZ failure and regional failure are different failure classes requiring different mechanisms.

Graceful shutdown from the target’s own perspective

Deregistration delay handles the load balancer’s side of a graceful shutdown, but the target itself needs to cooperate — an application that doesn’t watch for the deregistration signal (or the equivalent SIGTERM from its orchestrator) and instead is abruptly killed the moment infrastructure decides to terminate it can still drop in-flight work even with a generous deregistration delay configured. Real graceful shutdown is a two-sided contract between the load balancer’s draining window and the application’s own shutdown handling.

7Security

TLS termination as an architectural decision, not just a checkbox

Terminating TLS at the ALB (using an ACM certificate) offloads the CPU cost of encryption from your application fleet and centralizes certificate management, but it means traffic between the ALB and backend targets is unencrypted unless you separately configure HTTPS on that internal hop too — a detail that matters directly for compliance regimes requiring encryption in transit end-to-end, not just at the public-facing edge. NLB, by contrast, can perform TLS termination as well (TLS listener), or it can pass raw TCP straight through, leaving TLS termination entirely to the backend — a common choice when the backend needs to see the original, unmodified TLS handshake for mutual TLS authentication.

Security groups: the load balancer’s and the target’s, working together

ALB and NLB targets are protected by two independent security group layers: the load balancer’s own security group (controlling what can reach the load balancer itself) and the target’s security group (which, in a well-designed setup, only allows inbound traffic from the load balancer’s security group specifically, rather than from a broad CIDR range). This chained trust model — internet to ELB, ELB security group to target security group — is what lets a target refuse all direct traffic that didn’t pass through the load balancer at all, closing off a bypass path that direct-IP access would otherwise leave open.

AWS WAF integration on ALB and the layer it protects

AWS WAF attaches directly to ALB (and to CloudFront, and to API Gateway) to inspect and filter HTTP requests against rule sets — rate limiting, SQL injection patterns, known bad IP reputation lists — before they ever reach a target. Because WAF operates at Layer 7, it’s an ALB-native capability; NLB, operating below the HTTP layer, has no equivalent request-content inspection point, which is one of the concrete, protocol-level reasons an architecture that needs WAF-style protection must sit behind ALB (or CloudFront) rather than NLB.

Edge

WAF on ALB

Inspects HTTP request content before it reaches any backend target.

Transport

TLS termination choice

Decide explicitly whether encryption ends at the ELB or continues to the backend.

Network

Chained security groups

Targets accept traffic only from the load balancer’s security group, not the open internet.

Identity

Built-in authentication

ALB listener rules can require Cognito or OIDC authentication before forwarding a request at all.

Production Example — Zero-Trust Internal Services

Organizations building internal, zero-trust-style microservice meshes commonly place an internal ALB (not internet-facing) in front of each service, require OIDC authentication at the listener-rule level before any request reaches application code, and lock target security groups down to accept traffic only from the ALB’s security group — pushing authentication and network isolation into managed infrastructure rather than duplicating that logic inside every service.

Mutual TLS and why it usually pushes teams toward pass-through, not termination

Workloads that need mutual TLS — verifying the client’s certificate, not just the server’s — often can’t use ALB’s standard TLS termination cleanly, because the client certificate verification needs to happen against the actual backend’s identity requirements, not a generic load-balancer-level check. NLB’s TCP pass-through mode (or ALB’s more recent mTLS support, where available) lets the full handshake reach the backend intact. This is a concrete, protocol-level reason mTLS-heavy architectures — common in financial services and healthcare integrations — lean toward pass-through designs rather than blanket TLS termination at the edge.

Certificate rotation as an operational security concern, not a one-time setup step

ACM-issued certificates attached to an ALB or NLB listener renew automatically as long as the domain validation record remains in place, removing the manual certificate-renewal fire drills that used to be a recurring on-premises operational burden. The advanced failure mode here isn’t the renewal itself — it’s a DNS validation record being removed or a Route 53 hosted zone being restructured without accounting for the CNAME validation record ACM depends on, which silently breaks automatic renewal until someone notices an expiring certificate warning.

8Monitoring, Logging & Metrics

All ELB types publish detailed CloudWatch metrics, but as with the other advanced topics in this article, the metric that predicts trouble first differs by engine and by what layer of the stack you actually control.

SignalWhat It Tells YouWhy It’s a Leading Indicator
Target response timeBackend health independent of networkRising response time with stable load points to the backend, not the ELB
HTTPCode_ELB_5xxErrors generated by the load balancer itselfDistinguishes ELB-side failures from application-side 5xx errors
Unhealthy host countHow much of your fleet is currently out of rotationA rising trend here precedes a capacity crisis even if overall latency still looks fine
Active flow count (NLB)Concurrent connection volumeReveals connection-based load patterns invisible in a purely HTTP-request-count view

Access logs as the forensic record neither metric captures

ELB access logs (delivered to S3) record per-request details — client IP, request path, response code, processing time split by ELB-side and target-side latency, and which target actually handled the request. This latency split is the single most useful field during an incident: it immediately tells you whether time was spent inside the load balancer or inside your own application, which is exactly the ambiguity that aggregate CloudWatch metrics alone can’t resolve.

!
Advanced Trap

Access logging is not enabled by default on any ELB type. Teams that only discover this during a post-incident review lose the exact forensic detail — which target served a bad response, at what latency — that would have made root-causing the incident straightforward instead of speculative.

Separating ELB-side errors from application-side errors in alerting

HTTPCode_ELB_5xx (errors the load balancer itself generates — for example, no healthy targets available) and HTTPCode_Target_5xx (errors the backend generated) look identical to an end user but point to completely different root causes and completely different responders. An alerting setup that only tracks aggregate 5xx rate without this split forces every on-call engineer to manually dig through logs just to determine whether the load balancer or the application is the actual source of an incident — a triage step that a properly split alarm eliminates instantly.

Connection-level metrics as an early warning system on NLB

Because NLB doesn’t provide the rich HTTP-level detail ALB does, its most valuable leading indicators are connection-level: active flow count, new flow count, and reset counts. A rising reset count with stable connection volume often signals a backend that’s dropping connections it can’t handle — a warning sign that surfaces well before request-level symptoms would, precisely because NLB’s visibility operates one layer lower than ALB’s.

9Deployment & Cloud Integration

Cross-account and multi-VPC exposure

NLB is unique among ELB types in that it can be exposed to consumers in other VPCs or other AWS accounts through AWS PrivateLink without requiring VPC peering at all — a consumer creates a VPC endpoint that resolves privately to the service behind the NLB, and no route table or peering relationship needs to exist between the two VPCs. This is the mechanism behind most AWS-native and third-party “private SaaS” connectivity patterns, where a vendor exposes a service to customer VPCs without ever routing customer traffic across the public internet or requiring a full network peering trust relationship.

graph LR
  ConsumerVPC[Consumer VPC] -->|VPC Endpoint| PL[AWS PrivateLink]
  PL --> NLB[Service Provider NLB]
  NLB --> Targets[Backend Service Targets]
    

Fig 9.1 — PrivateLink lets a consumer VPC reach a service behind an NLB without VPC peering or public internet exposure, a pattern unique to NLB among ELB types.

Infrastructure-as-code and the immutability of certain choices

The ELB type itself (ALB vs NLB vs GWLB) cannot be changed after creation — switching engines means creating a new load balancer and cutting DNS over, which is precisely why production deployments define ELB configuration declaratively (CloudFormation, CDK, Terraform) so that the type, listener rules, target group health-check settings, and security group wiring are all reproducible exactly, rather than reconstructed from memory during a disaster-recovery rebuild.

Container and serverless integration as a first-class registration model

ALB integrates directly with ECS and EKS service registration, automatically registering and deregistering tasks or pods as they scale, and can route directly to a Lambda function as a target — collapsing what used to require a separate service-discovery layer (or an API Gateway in front of Lambda) into the load balancer’s own target-group model. This matters architecturally because it means the same listener-rule routing logic (path-based, host-based) applies uniformly whether the backend is a long-running EC2 fleet, a container task, or a serverless function, without the client or the routing layer needing to know which.

Elastic IP association and outbound traffic implications

Because NLB can be assigned Elastic IPs per AZ, it’s also the standard mechanism for giving outbound traffic from a fleet a stable, allow-listable source identity when combined appropriately with routing — a requirement that recurs constantly in partner API integrations that require IP allow-listing on their end. ALB’s shared, DNS-resolved IP pool cannot offer this same guarantee, which is a concrete, protocol-level reason NLB gets selected even for workloads that would otherwise be a natural ALB fit.

10Design Patterns & Anti-patterns

Pattern: ALB path-based routing as a lightweight API gateway

Teams running a handful of backend services often route `/orders/*`, `/users/*`, and `/payments/*` to separate target groups behind a single ALB, using listener rules as a lightweight routing layer instead of standing up a dedicated API gateway product for cases that don’t need advanced request transformation or usage-plan throttling.

Pattern: NLB in front of ALB for static IP requirements

ALB doesn’t expose static IP addresses directly, which breaks designs that need a fixed IP for allow-listing (some partner integrations or legacy firewall rules require this). The standard advanced pattern is placing an NLB (which does support static or Elastic IPs per AZ) in front of the ALB as a target, getting NLB’s static-IP property while retaining ALB’s Layer-7 routing intelligence for the actual traffic.

Pattern: GWLB for centralized, transparent traffic inspection

Rather than routing traffic through a security appliance as an explicit network hop that every VPC’s route tables must know about, teams register the appliance fleet behind a GWLB endpoint and use VPC route table entries that redirect relevant traffic through it transparently — centralizing inspection logic for many VPCs behind one managed, auto-scaling appliance layer instead of duplicating firewall infrastructure per VPC.

Anti-pattern: assuming NLB preserves client IP the same way ALB does after a proxy hop

NLB preserves client IP end-to-end by default, which is exactly why teams sometimes port IP-based access-control logic straight from an on-prem NLB-fronted design onto ALB and are surprised when every request appears to originate from the ALB’s own IP instead — the fix is reading X-Forwarded-For explicitly on ALB, not assuming identical behavior across engines.

Pattern: fixed-response actions for graceful degradation

ALB listener rules can return a fixed response — a static maintenance page, a rate-limit message, a redirect — directly from the load balancer without ever reaching a backend target. Advanced designs use this as a circuit-breaker of last resort: if a target group’s health drops below a usable threshold, a listener rule can be flipped to serve a graceful “temporarily unavailable” response instead of routing traffic into an overwhelmed, thrashing backend fleet, buying recovery time without a full outage-page deployment.

Anti-pattern: one giant target group for logically separate services

Registering unrelated services against a single target group to save on load-balancer count removes the ability to health-check, scale, and deploy those services independently — a failure or slow deployment in one service degrades health-check signal and routing behavior for all of them, since the load balancer has no way to distinguish “this is service A’s problem” from “this is service B’s problem” once they share a target group.

Good Fit Patterns

  • ALB for HTTP microservices needing path/host-based routing and WAF protection
  • NLB for extreme-throughput TCP/UDP workloads or static-IP requirements
  • GWLB for centralizing third-party security appliance insertion across many VPCs

Poor Fit Patterns

  • NLB for workloads that need HTTP-header-based routing decisions
  • ALB for ultra-low-latency, extreme-connection-count TCP workloads
  • CLB for any new, greenfield architecture

11Best Practices & Common Mistakes

Health Checks

Make them meaningful

Check real downstream dependencies, not just process liveness.

Draining

Tune deregistration delay deliberately

Match it to your longest legitimate request duration, not a copied default.

Cross-Zone

Keep target counts even per AZ

Especially critical on NLB, where cross-zone routing is off by default.

Logging

Enable access logs before you need them

The latency-split field is irreplaceable during a real incident.

DNS

Never pin to a resolved IP

The DNS name is the only stable contract ELB’s HA design guarantees.

Client IP

Verify X-Forwarded-For usage

Don’t assume ALB preserves client IP the way NLB does by default.

The most common mistake across every engine

Treating “it’s a managed load balancer” as a reason to skip capacity planning entirely. ELB scales itself, but the backend fleet behind it does not scale itself, and the load balancer’s own scaling response has real, non-zero latency for gradual-scaling engines like ALB. Skipping load testing before a known traffic event, on the assumption that the load balancer alone will absorb it, is the single most repeated production incident pattern across ELB-fronted architectures.

The second most common mistake: leaving default timeouts and thresholds untouched

Idle timeout, deregistration delay, health-check interval, and healthy/unhealthy thresholds all ship with reasonable generic defaults — and reasonable generic defaults are, by definition, not tuned for any specific workload. Long-running upload endpoints, WebSocket connections, and slow-starting applications each need at least one of these values adjusted deliberately; leaving every timeout at its default because “it works in the demo” is exactly how a design passes a proof-of-concept and then fails its first real production traffic pattern.

The third most common mistake: no plan for the load balancer’s own limits during an incident

Even a self-scaling load balancer has finite scaling speed and finite absolute ceilings under extreme, unannounced conditions. Advanced operational readiness includes knowing, ahead of time, whether a specific workload’s traffic pattern warrants pre-warming, whether NLB’s higher burst tolerance makes it the safer choice for that pattern, and what the actual escalation path to AWS Support looks like if a genuinely unprecedented spike is anticipated — none of which is useful information to be discovering for the first time in the middle of an active incident.

12Real-World & Industry Examples

E-Commerce — Flash Sale Traffic Spikes

Retailers running scheduled flash sales frequently request ALB pre-warming ahead of the event or architect the entry point around NLB specifically because its scaling characteristics tolerate a sudden ten-times-normal spike without the gradual ramp-up ALB’s node fleet needs.

Financial Services — Low-Latency Trading Gateways

Trading platforms where microseconds matter route order-entry traffic through NLB specifically for its packet-forwarding model, avoiding the connection-splicing overhead ALB’s full HTTP termination would introduce on every single order message.

SaaS Platforms — Private Connectivity to Customer VPCs

B2B SaaS vendors expose their service to customer VPCs through an NLB fronted by AWS PrivateLink, letting each customer reach the service privately without a VPC peering relationship or public internet exposure — a pattern that scales to thousands of customer connections without thousands of individual network relationships to manage.

Enterprise Security — Centralized Firewall Insertion

Enterprises running a shared services VPC with a fleet of third-party next-generation firewalls place that fleet behind a GWLB and use route-table redirection across dozens of application VPCs, centralizing inspection policy in one managed layer instead of deploying firewall infrastructure per VPC.

Media Streaming — Path-Based Microservice Routing

Streaming platforms serving a mobile app, a web app, and a partner API from a shared backend use a single ALB with host-header and path-based listener rules to route each surface to its own target group and independently scale each one, rather than provisioning separate public-facing load balancers per surface.

Gaming — Static-IP Requirements for Game Server Fleets

Multiplayer gaming backends that need to hand players a fixed, allow-listable server IP for their console or client firewall rules front their fleet with NLB specifically for its Elastic IP support, something ALB’s shared DNS-based IP pool structurally cannot provide.

Across all six of these patterns, the same underlying discipline recurs: the choice of ELB engine was never a matter of preference — it was dictated by a specific protocol-level requirement (raw throughput, static IPs, transparent inspection, request-content routing) that only one of the four engines could actually satisfy.

13FAQ

Q1Why doesn’t my backend see the real client IP behind an ALB?
ALB terminates the client connection and opens a new one to the backend, so by default the backend sees the ALB’s own IP as the source. The original client IP is preserved in the X-Forwarded-For header, which the application must read explicitly rather than relying on the raw connection source.
Q2Why is cross-zone load balancing disabled by default on NLB but not ALB?
It’s a deliberate design trade-off tied to preserving NLB’s extreme performance characteristics — routing strictly within an AZ avoids extra cross-AZ hops on every packet. ALB’s connection-splicing model doesn’t carry the same performance sensitivity, so it distributes evenly across AZs by default.
Q3Can I get a static IP address for an Application Load Balancer?
Not directly. The standard pattern is placing an NLB (which supports static or Elastic IPs) in front of the ALB as its target, combining NLB’s static-IP property with ALB’s Layer-7 routing.
Q4Does enabling HTTPS on the load balancer automatically encrypt traffic all the way to my backend?
No. TLS termination at the load balancer only guarantees encryption up to that point. The hop from the load balancer to the backend target is a separate configuration decision, and it remains unencrypted unless HTTPS or another encrypted protocol is explicitly configured on that internal connection too.
Q5What actually happens during a deployment if deregistration delay is set too short?
In-flight requests still being processed by a target being deregistered get forcibly cut off once the delay window expires, which surfaces to clients as failed or incomplete responses during otherwise routine deployments or scale-in events.
Q6Why would I ever choose Gateway Load Balancer instead of just configuring my own firewall routing?
GWLB turns appliance insertion into a managed, auto-scaling primitive with built-in health checking of the appliance fleet, rather than a hand-maintained set of route tables and manually-scaled firewall instances that someone has to operate and scale themselves.
Q7Is Classic Load Balancer ever the right choice for a new project?
Essentially no. CLB predates target groups and lacks the routing flexibility, health-check granularity, and native integrations that ALB and NLB provide. It’s relevant mainly for recognizing legacy architecture during a migration assessment, not for new designs.
Q8Does a Multi-AZ load balancer protect against a full AWS Region outage?
No. ELB is a regional resource scoped to Availability Zones within one Region. Protecting against a regional event requires a layer above ELB, typically Route 53 health-checked failover or latency-based routing across independently deployed load balancer stacks in separate Regions.

14Summary & Key Takeaways

Every chapter above points back to one discipline: pick the ELB engine that matches the actual layer your traffic needs intelligence at — HTTP semantics, raw packet throughput, or transparent appliance insertion — and then design health checks, draining windows, and cross-zone behavior around that engine’s specific model rather than a generic “load balancer” mental model that doesn’t survive a real production incident.

The deepest lesson underneath every section here is that Elastic Load Balancing was never one product wearing four skins — it’s four independent engineering solutions to four different traffic problems, unified only by a shared management surface. Treating them as interchangeable, or assuming one engine’s behavior transfers to another, is where nearly every advanced ELB incident in this article traces back to.

What to carry forward

  • ELB is four engines, not one. ALB, NLB, GWLB, and CLB solve different problems at different layers of the network stack, with different scaling and failure characteristics.
  • ALB terminates and re-originates connections; NLB forwards packets by flow hash and preserves client IP; GWLB transparently inserts third-party appliances via GENEVE tunneling.
  • Cross-zone load balancing defaults differ by engine — NLB is off by default, which makes even target distribution across AZs a real operational requirement, not a nice-to-have.
  • Deregistration delay is a trade-off with no free setting. Tune it to your actual longest legitimate request, not a copied default.
  • Health checks define the reliability boundary between the load balancer’s own high availability and your backend fleet’s actual ability to serve traffic — shallow checks create a false sense of safety.
  • The DNS name is the only stable contract ELB’s multi-AZ design guarantees; pinning to a resolved IP anywhere reintroduces the single point of failure the architecture was built to avoid.
  • Access logs, not just CloudWatch metrics, are the forensic record that distinguishes load-balancer-side latency from backend-side latency during an incident — enable them before you need them.