AWS Lesson 35 of 123

AWS Elastic Load Balancing, In Depth: ALB, NLB, GWLB & Target Groups

In a nutshell

Picture the single front door of a busy restaurant. Every guest arrives at one address, a host greets them there, and the host decides which of the many waiters and tables to send each guest to — quietly skipping any waiter who’s on a break. Guests never need to know which staff came or went; the door stays the same. A load balancer is that host for your application: clients reach one stable DNS name, and behind it the balancer spreads every request across a fleet of backends, steps around the ones that are unhealthy, and hides all the churn of instances scaling in and out.

AWS gives you three modern hosts for three different jobs. An ALB (Application Load Balancer) is the host who reads your reservation — it looks inside each HTTP request (the URL, the hostname, the headers) and routes accordingly, so /api goes to one kitchen and /images to another. An NLB (Network Load Balancer) is a turnstile: it doesn’t read anything, it just waves connections through at enormous speed and can keep the exact same street address (a static IP) forever. A GWLB (Gateway Load Balancer) is the host who walks every guest past a security checkpoint first, feeding traffic through firewall appliances before it reaches you.

Get the choice and the health checks right and your app is highly available and scales itself; get them wrong and you meet the 3-a.m. 503 page. This lesson is the exhaustive tour — read the analogy above, then everything below fills in every knob.

Level: Intermediate · Time: ~50 min · Full prerequisites and outcomes are in the two sections just below.

Almost nothing you build on AWS talks to the internet directly. In front of your EC2 instances, your containers, your Lambda functions, and even your on-prem servers sits a load balancer — the single, stable front door that takes incoming connections and spreads them across a fleet of backends, hides individual instance failures, terminates TLS, and gives you one DNS name that survives every scale-out, deploy, and instance replacement underneath it. Get it right and your application is highly available, horizontally scalable, and secure by default. Get it wrong and you discover, usually at 3 a.m., that health checks were failing silently, that cross-zone traffic was lop-sided, that sticky sessions pinned everyone to one dying instance, or that you paid for a Network Load Balancer when you needed Layer-7 path routing.

Elastic Load Balancing (ELB) is the AWS service that gives you four flavours of load balancer for four different jobs: the Application Load Balancer (ALB) for HTTP/HTTPS and content-based routing at Layer 7; the Network Load Balancer (NLB) for raw TCP/UDP throughput and ultra-low latency at Layer 4; the Gateway Load Balancer (GWLB) for inserting third-party security appliances transparently at Layer 3; and the Classic Load Balancer (CLB), the original, now legacy and slated for retirement. This is the exhaustive lesson. We go through the whole family — the comparison that decides which one you pick, then listeners and routing rules, target groups in every shape (instance, IP, Lambda, ALB-as-target), health checks down to the field, deregistration delay, the three stickiness modes, slow start, cross-zone load balancing and its billing twist, TLS termination with SNI and mutual TLS, access logs, and the security-group model that differs between ALB and NLB — until you can whiteboard an AWS load-balancing design from memory and answer every follow-up the SAA and Advanced Networking exams will throw at you.

Learning objectives

Prerequisites & where this fits

You need an AWS account, the AWS CLI configured (see AWS Hands-On First Steps), and a working mental model of a VPC — subnets across Availability Zones, route tables, and security groups vs NACLs. If those are shaky, read Amazon VPC, In Depth and AWS Security Groups vs Network ACLs, In Depth first — this lesson assumes you know what a subnet and a security group are and goes deep on every load-balancer knob. This is the Networking deep-dive of the AWS Zero-to-Hero course, the layer that sits between your VPC and your compute. It pairs naturally with EC2 Auto Scaling (a load balancer’s target group is what an Auto Scaling group registers into) and with Route 53, which puts a friendly name in front of the load balancer’s own DNS name — the next lesson, Amazon Route 53, In Depth, covers that.

Core concepts

A load balancer accepts incoming connections on one side and distributes them across a pool of targets on the other, so that no single backend is a bottleneck or a single point of failure. The AWS implementation is a fully managed, horizontally scaling fleet of nodes that AWS runs for you — you never see or patch the underlying instances; you just get a DNS name and an endpoint that scales with load.

Anchor everything that follows on these mental models:

Key terms you’ll see throughout: target group (the pool of backends + health check), listener (protocol/port the LB accepts on), rule (ALB condition→action), deregistration delay / connection draining (the grace period before a removed target stops receiving traffic), stickiness / session affinity (pinning a client to one target), SNI (Server Name Indication — multiple TLS certificates on one listener), mTLS (mutual TLS — the client also presents a certificate), and cross-zone load balancing (whether nodes spread traffic across AZs).

The ELB family: ALB vs NLB vs GWLB vs CLB

Four load balancers, four jobs. Three of them — ALB, NLB, GWLB — share a modern code base and are collectively the v2 / “Elastic Load Balancing v2” generation built on the target-group model. The CLB is the v1 original, predating target groups, and AWS has announced its retirement; treat it as legacy only.

Capability ALB (Application) NLB (Network) GWLB (Gateway) CLB (Classic — legacy)
OSI layer 7 (application) 4 (transport) 3 (network) + 4 4 and 7 (limited)
Protocols HTTP, HTTPS, gRPC, HTTP/2, WebSocket TCP, UDP, TCP_UDP, TLS IP (GENEVE encapsulation, port 6081) TCP, SSL, HTTP, HTTPS
Routing intelligence Host, path, header, query, method, source IP By flow (5-tuple hash) — no content awareness Transparent — forwards all packets to appliances Round-robin / least-outstanding; basic
Performance High; adds a few ms latency Millions of req/s, ultra-low latency, sub-ms High; bump-in-the-wire Lower; older engine
Static IP / Elastic IP No (DNS name only) Yes (one EIP per AZ) No (uses endpoints) No
Preserve client source IP No (use X-Forwarded-For header) Yes by default (instance/IP targets) Yes (encapsulated) No (XFF for HTTP)
Security group Yes (attached to the ALB) Yes (since Aug 2023) No Yes
Target types instance, IP, Lambda, ALB instance, IP, ALB n/a (GWLB endpoints) instance only
TLS termination Yes (+ SNI, mTLS) Yes (TLS listener; + mTLS) No (passes through) Yes (SSL)
WAF integration Yes (AWS WAF attaches to ALB) No No No
Authentication (OIDC/Cognito) Yes No No No
Sticky sessions Duration cookie + app cookie Source-IP affinity n/a Duration cookie
Slow start Yes No No No
Typical use Web apps, microservices, API routing Extreme TCP/UDP throughput, static IP, PrivateLink front Inline firewalls/IDS/IPS appliances Existing EC2-Classic / don’t use for new

The decision in one breath: HTTP/HTTPS with any content-based routing → ALB. Raw TCP/UDP, static IPs, extreme performance, or fronting a PrivateLink service → NLB. Inserting third-party security appliances inline → GWLB. New workload → never CLB. The “when each” section at the end works through the edge cases.

Application Load Balancer (ALB) — Layer 7

The ALB speaks HTTP. It terminates the connection, parses the request, and routes on anything in the request: the Host header, the URL path, arbitrary headers, query strings, the HTTP method, even the source IP. That intelligence is why it’s the default for web applications and microservices — one ALB can route /api/* to one service, /static/* to another, admin.example.com to a third, all from a single listener. It supports HTTP/1.1, HTTP/2, gRPC, and WebSockets, integrates with AWS WAF for L7 protection, and can authenticate users via OIDC or Amazon Cognito before the request ever reaches your code. Because it operates at L7, it does not preserve the client’s source IP at the TCP layer — your application reads the original client address from the X-Forwarded-For header instead. It always uses a DNS name (no static IP) and always has a security group.

Network Load Balancer (NLB) — Layer 4

The NLB doesn’t read your traffic — it forwards flows. It hashes the connection’s 5-tuple (source IP, source port, destination IP, destination port, protocol) to pick a target and sends the packets on, which makes it astonishingly fast: millions of requests per second, single-digit-millisecond (often sub-millisecond) latency, and the ability to scale to sudden, spiky traffic without pre-warming. It’s the only ELB that can have a static Elastic IP per AZ — essential when clients need to allowlist your IPs by firewall rule. It preserves the client source IP by default for instance and IP targets, handles TCP, UDP, TCP_UDP, and TLS listeners, and is the load balancer you put behind an AWS PrivateLink endpoint service. Since August 2023 NLBs support security groups too (and you must decide at creation time — you cannot add one to an SG-less NLB later). Reach for the NLB when you need extreme performance, non-HTTP protocols, static IPs, or source-IP preservation.

Gateway Load Balancer (GWLB) — Layer 3

The GWLB exists for one job: inserting third-party network appliances — firewalls, IDS/IPS, deep packet inspection — transparently into your traffic path, as a “bump in the wire”. It operates at Layer 3, distributing IP packets across a fleet of appliances and using the GENEVE protocol (UDP port 6081) to encapsulate traffic so the appliance sees the original packet unchanged. You don’t point clients at a GWLB directly; you create Gateway Load Balancer Endpoints (a PrivateLink type) and route traffic to them with VPC route tables. It preserves flow stickiness so a connection’s packets always reach the same appliance. If you’re deploying a Palo Alto, Fortinet, Check Point, or similar virtual appliance for centralised inspection, the GWLB is the AWS-native way to do it — see AWS Network Firewall: egress filtering & centralized inspection for the AWS-managed alternative.

Classic Load Balancer (CLB) — legacy

The original ELB from 2009, predating target groups and the v2 generation. It straddles L4 and a limited L7, supports only instance targets, has none of the modern routing rules, no SNI, no Lambda targets, and a weaker feature set across the board. AWS has announced the retirement of the Classic Load Balancer and is actively steering customers off it. The only reason to touch a CLB today is to migrate an old workload away from it. Every new design uses ALB or NLB.

Listeners and rules

A listener is the front door: a protocol and port the load balancer accepts connections on. You can have multiple listeners on one load balancer (e.g. an ALB with both HTTP:80 — usually redirecting — and HTTPS:443). What a listener does with a connection differs by load-balancer type: on an NLB and CLB the listener simply forwards to a target group (or, on CLB, to instances); on an ALB, the listener runs an ordered list of rules, and this is where the L7 power lives.

Listener fields

Field What it does Choices / default When to change / gotcha
Protocol The protocol the listener accepts ALB: HTTP, HTTPS. NLB: TCP, UDP, TCP_UDP, TLS. Match the client’s protocol; TLS/HTTPS unlocks certificate and security-policy fields.
Port The port clients connect on 1–65535 80/443 are conventional; nothing stops you using others. One listener per port.
Default action What happens when no rule matches (ALB) or for every connection (NLB) forward / redirect / fixed-response / authenticate Always set a sensible default — often a fixed 404 or a redirect, so unmatched traffic isn’t accidentally forwarded.
SSL/TLS certificate The server certificate (HTTPS/TLS only) ACM or IAM certificate Use ACM — free public certs, auto-renewal. One default cert; add more via SNI.
Security policy The negotiable TLS protocols/ciphers A named ELBSecurityPolicy-* Pick a current TLS-1.2/1.3 policy; older policies allow weak ciphers (see Security notes).
mTLS mode Whether clients must present a certificate (ALB HTTPS) off / verify / passthrough Turn on only when you need client-certificate auth; see the mTLS section.
ALPN Application-Layer Protocol Negotiation (NLB TLS) None / HTTP1Only / HTTP2Only / HTTP2Optional / etc. Set for HTTP/2 over a TLS NLB listener.

ALB rules: conditions and actions

Each ALB listener has a default rule (the catch-all) plus up to 100 custom rules, evaluated in priority order, lowest number first; the first rule whose conditions all match wins, and evaluation stops. A rule is a set of conditions (ANDed together) and exactly one terminal action (plus optional non-terminal actions like authenticate that run first).

Rule conditions — the things you can match on:

Condition Matches on Example
Host header The Host: / domain shop.example.com, *.example.com
Path The URL path /api/*, /images/*
HTTP header Any request header (name + value) X-Tenant: acme
HTTP request method GET/POST/PUT/DELETE/… route writes vs reads differently
Query string ?key=value pairs ?version=beta
Source IP Client CIDR 203.0.113.0/24 (allowlist an office)

Conditions support wildcards (*, ?) and you can have several values per condition. All conditions in a rule must match for the rule to fire.

Rule actions — what happens when a rule matches:

Action What it does Notes
forward Send to one target group The bread-and-butter action.
weighted forward Split across multiple target groups by weight Enables blue/green and canary at the load-balancer layer; combine with target-group stickiness to keep a session on one group.
redirect Return a 301/302 to a new URL The standard HTTP→HTTPS redirect; can rewrite host, path, port, query.
fixed-response Return a hard-coded status + body Maintenance pages, blocking, health endpoints without a backend.
authenticate-oidc Require login via an OIDC IdP Runs before forward; offloads auth from your app.
authenticate-cognito Require login via Amazon Cognito Same idea, Cognito user pools.

A canonical setup: an HTTP:80 listener whose default action is a redirect to HTTPS, and an HTTPS:443 listener with rules — Host shop.example.com + Path /api/* → forward to tg-api; Path /static/* → forward to tg-static; default → forward to tg-web. That’s content-based routing, and it’s why teams pick the ALB.

# Add an HTTPS listener with a default forward to a target group
aws elbv2 create-listener \
  --load-balancer-arn "$ALB_ARN" \
  --protocol HTTPS --port 443 \
  --certificates CertificateArn="$ACM_CERT_ARN" \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --default-actions Type=forward,TargetGroupArn="$TG_WEB_ARN"

# Add a path-based rule: /api/* -> tg-api, priority 10
aws elbv2 create-rule \
  --listener-arn "$LISTENER_ARN" \
  --priority 10 \
  --conditions Field=path-pattern,Values='/api/*' \
  --actions Type=forward,TargetGroupArn="$TG_API_ARN"

# HTTP:80 listener that redirects everything to HTTPS
aws elbv2 create-listener \
  --load-balancer-arn "$ALB_ARN" \
  --protocol HTTP --port 80 \
  --default-actions \
  'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'

Target groups

A target group is the pool of backends a listener or rule forwards to, plus the health check that decides which of those backends are eligible, plus the per-group attributes (deregistration delay, stickiness, slow start, etc.). Target groups are first-class resources you create independently and attach to listeners — and one target group can be referenced by multiple listeners or rules, even across more than one load balancer. The most important decision is the target type, because it’s fixed at creation and changes everything else.

Target types

Target type What you register Source IP your app sees When to use Gotcha
instance EC2 instance IDs The LB’s private IP (ALB) / client IP (NLB) The default for EC2 fleets behind an ALB/NLB; Auto Scaling registers here Targets must be in the LB’s VPC; routes traffic to the instance’s primary IP.
ip IP addresses (in-VPC, peered VPC, or on-prem over VPN/DX) Same as instance Containers (ECS awsvpc), on-prem servers, peered VPCs, higher target density You manage registration; IPs must be RFC1918 within reachable ranges (or the VPC CIDR).
lambda A Lambda function n/a (event payload) Serverless backends behind an ALB ALB only; the ALB invokes the function and maps the response to HTTP.
alb Another Application Load Balancer n/a Front an ALB with an NLB (static IP / PrivateLink in front of L7 routing) Target type alb is for an NLB target group pointing at an ALB.

The alb target type is the clever one: it lets you put an NLB in front of an ALB, so you get the NLB’s static Elastic IPs and PrivateLink support plus the ALB’s Layer-7 routing behind it — the standard way to expose an HTTP service over PrivateLink.

Target group core settings

Setting What it does Values / default When to change / gotcha
Target type Instance / IP / Lambda / ALB As above; fixed at creation Choose deliberately; to change it you recreate the group.
Protocol Protocol to the targets HTTP/HTTPS (ALB); TCP/UDP/TLS (NLB) Use HTTP to targets unless you need end-to-end encryption (then HTTPS/TLS — re-encryption).
Port Port on the target 1–65535 The port your app listens on; for dynamic-port containers, the registration overrides it.
Protocol version HTTP/1.1, HTTP/2, or gRPC HTTP1 (default) Set gRPC for gRPC services; HTTP2 for h2c to targets.
VPC The VPC the targets live in Any VPC the LB can reach IP targets can be in peered VPCs / on-prem; instance targets must match the LB’s VPC.
IP address type IPv4 or IPv6 targets IPv4 (default) Match your fleet; dual-stack ALBs can have IPv6 target groups.

Health checks — field by field

The health check is the most consequential thing in a target group. The load balancer probes each target on a schedule; a target becomes healthy after N consecutive passes and unhealthy after M consecutive failures, and only healthy targets receive traffic.

Field What it does Default (ALB) Notes / gotcha
Protocol Protocol used to probe HTTP NLB can health-check with TCP/HTTP/HTTPS even for a TCP listener — prefer an HTTP check so you test the app, not just the port.
Path The URL path probed (HTTP/HTTPS) / Point it at a real /healthz that checks dependencies — but not so deep it cascades failures.
Port Port to probe traffic-port (same as the target port) Override to use a dedicated health port if your app exposes one.
Healthy threshold Consecutive passes to mark healthy 5 (ALB) / 3 (NLB) Higher = slower to add a recovered target; lower = faster but flappier.
Unhealthy threshold Consecutive failures to mark unhealthy 2 Lower = quicker to eject a bad target; too low flaps on transient blips.
Timeout Seconds to wait for a probe response 5s (ALB) Must be less than the interval. A slow /healthz causes false failures.
Interval Seconds between probes 30s (ALB) / 30s (NLB) Lower for faster detection (min 5s); raises probe volume on targets.
Success codes (Matcher) HTTP codes counted as healthy 200 (ALB) Use a range like 200-299 if your health endpoint can return 204; gRPC uses status codes.

The classic failure mode: a security group that doesn’t allow the load balancer’s traffic to the health-check port, so every target shows unhealthy and the ALB returns 503 Service Unavailable. Always allow the LB’s SG (ALB) or the LB nodes’ subnet/CIDR (NLB) inbound to the target on both the traffic port and the health-check port.

Deregistration delay (connection draining)

When a target is deregistered — because Auto Scaling is terminating it, you’re deploying, or it failed — you rarely want to cut its in-flight requests dead. Deregistration delay (a.k.a. connection draining) is the grace period during which the load balancer stops sending new requests to the target but lets existing connections finish.

Attribute What it does Default When to change
deregistration_delay.timeout_seconds Seconds to wait for in-flight requests to drain before fully removing the target 300s Lower (e.g. 30s) for stateless, short-request web apps to speed deploys; raise for long-lived requests/uploads.
deregistration_delay.connection_termination.enabled (NLB) Whether to forcibly terminate connections at the end of the delay false Set true to hard-close lingering NLB flows.

Tie this to your Auto Scaling lifecycle hooks and ALB health-check grace period so instances drain cleanly on scale-in and on rolling deploys.

Stickiness (session affinity)

By default a load balancer spreads requests; stickiness pins a client to the same target for a window, which matters for apps that hold session state in memory. There are three flavours:

Mode How it works Where When to use / gotcha
Duration-based (LB cookie) The ALB issues its own cookie (AWSALB) and routes that client back to the same target for the configured duration (1s–7 days) ALB target group Simple session affinity; the LB owns the cookie. Defeats even load spreading.
Application-based cookie The ALB honours a cookie your application sets (you name it; the ALB wraps it in AWSALBAPP) ALB target group Use when the app already manages a session cookie and you want affinity tied to it.
Source-IP affinity The NLB hashes the client source IP (and optionally port) to a fixed target NLB target group The L4 equivalent; note clients behind one NAT all land on the same target.

Stickiness is a smell as often as a solution: it undermines even distribution and means a target’s death takes its pinned sessions with it. Prefer stateless backends with session state in ElastiCache/DynamoDB, and use stickiness only when you truly cannot.

Slow start

When a fresh target joins a busy target group, sending it a full share of traffic immediately can overwhelm a cold JVM, an empty cache, or a connection pool that hasn’t warmed. Slow start ramps a new target’s traffic linearly from zero to its full share over a configured window (30–900 seconds; 0 = disabled, the default).

Attribute What it does Default When to change
slow_start.duration_seconds Linear ramp-up window for newly healthy targets 0 (off) Set 30–300s for JIT-compiled / cache-warming apps so new instances aren’t crushed on join.

Slow start applies to ALB and CLB target groups only (not NLB), and it does not apply while a target group is empty or only has one target — there’s no one else to share with.

Routing algorithm — how a target actually gets picked

The health check decides which targets are eligible; the routing algorithm decides which eligible target a given request goes to. It’s set per target group and differs by load-balancer type, and it’s the detail that explains a lot of “why is one instance hotter than the others?” mysteries.

Load balancer Algorithm(s) Attribute / default What it means
ALB Round robin (default), least outstanding requests (LOR), weighted random load_balancing.algorithm.type = round_robin Round robin hands each new request to the next target in turn — great when requests are uniform. LOR sends each request to the healthy target with the fewest in-flight requests — far better when request costs vary wildly or connections are long-lived (WebSockets, streaming), because a slow target stops attracting new work. Weighted random distributes randomly and is the mode that enables Automatic Target Weights (ATW) anomaly mitigation (below).
NLB Flow hash Not configurable The NLB hashes the connection’s tuple — protocol, source IP/port, destination IP/port, and (for TCP) the TCP sequence number — to pick a target, and every packet of that flow goes to the same target for the life of the connection. This is why an NLB is “sticky by connection” without you configuring anything.
CLB Round robin (TCP listeners) / least outstanding requests (HTTP/HTTPS) Not configurable Legacy behaviour; noted for completeness.

Two consequences worth internalising. First, stickiness overrides the algorithm: once a client is pinned (by the AWSALB cookie on an ALB or the flow hash on an NLB) that decision wins, so heavy stickiness can defeat even LOR. Second, on the ALB, least_outstanding_requests is usually the better default for real microservices — flip it on any target group whose requests are non-uniform:

aws elbv2 modify-target-group-attributes \
  --target-group-arn "$TG_ARN" \
  --attributes Key=load_balancing.algorithm.type,Value=least_outstanding_requests

Automatic Target Weights (ATW) is the newer safety net: with weighted_random, the ALB continuously measures per-target error rates and automatically shifts traffic away from a target that is failing anomalously (returning 5xx, connection errors) even while its health check still passes — catching the “healthy but broken” grey failure that a binary health check misses.

Cross-zone load balancing

A load balancer has one node per Availability Zone. Cross-zone load balancing decides whether each node may send traffic only to targets in its own AZ or to all registered targets across every AZ. This single setting changes both distribution fairness and your bill, and — crucially — the default differs by load-balancer type.

Behaviour Cross-zone ON Cross-zone OFF
Where a node sends traffic Any healthy target in any AZ Only healthy targets in the node’s own AZ
Distribution Even across all targets regardless of per-AZ counts Even across AZs first, so uneven per-AZ target counts → uneven per-target load
Inter-AZ data transfer cost ALB: free. NLB/GWLB: charged for cross-zone traffic None (traffic stays in-AZ)
Default ALB: ON (always; not configurable off at LB level — set per target group). NLB & GWLB: OFF. CLB via console: ON; via API/CLI: OFF

Why the uneven-distribution warning matters: imagine AZ-a has 1 instance and AZ-b has 4, and Route 53 sends roughly half the clients to each AZ’s node. With cross-zone off, the single instance in AZ-a gets the whole of AZ-a’s 50% while each of AZ-b’s four instances gets 12.5% — a 4× imbalance. With cross-zone on, all five instances share equally. The defence is to keep target counts balanced across AZs (Auto Scaling’s AZ rebalancing does this) or turn cross-zone on.

The billing twist: for the ALB cross-zone traffic is free, so leaving it on is a no-brainer. For the NLB and GWLB, cross-zone traffic incurs inter-AZ data-transfer charges, so enabling it trades even distribution for cost — on a high-throughput NLB that can be significant. On v2 load balancers cross-zone is configured as a target-group attribute (load_balancing.cross_zone.enabled), so you can even mix policies per target group.

# Turn cross-zone ON for an NLB target group (off by default)
aws elbv2 modify-target-group-attributes \
  --target-group-arn "$TG_ARN" \
  --attributes Key=load_balancing.cross_zone.enabled,Value=true

TLS termination, SNI and mTLS

TLS termination means the load balancer decrypts HTTPS/TLS at the edge so your backends can speak plain HTTP — offloading the cryptographic work, centralising certificate management, and letting the ALB read the request for routing. (You can also re-encrypt to the targets by using an HTTPS/TLS target-group protocol when end-to-end encryption is required.)

Certificates and security policies

You attach a server certificate to an HTTPS (ALB) or TLS (NLB) listener. Use AWS Certificate Manager (ACM) — public certificates are free and auto-renew, so you never get paged for an expired cert. The security policy (ELBSecurityPolicy-*) is the named set of TLS protocol versions and cipher suites the listener will negotiate; pick a current TLS 1.2/1.3 policy (e.g. ELBSecurityPolicy-TLS13-1-2-2021-06) so you don’t allow legacy TLS 1.0/1.1 or weak ciphers.

SNI — many certificates, one listener

Server Name Indication (SNI) lets a single HTTPS listener serve multiple TLS certificates and present the right one based on the hostname the client requests in the TLS handshake. You set one default certificate and add others; the ALB matches the SNI hostname to the best certificate (a NLB TLS listener supports SNI too). This is how one ALB fronts app.example.com, api.example.com, and customer-vanity-domain.com each with its own certificate — no need for a load balancer per domain.

# Add an extra SNI certificate to an existing HTTPS listener
aws elbv2 add-listener-certificates \
  --listener-arn "$LISTENER_ARN" \
  --certificates CertificateArn="$SECOND_CERT_ARN"

Mutual TLS (mTLS)

In ordinary TLS the server proves its identity to the client. In mutual TLS the client also presents a certificate that the load balancer validates — strong, certificate-based client authentication for B2B APIs, IoT fleets, and zero-trust designs. The ALB supports mTLS on HTTPS listeners in two modes:

The NLB also supports a form of TLS mutual authentication on TLS listeners. Use mTLS when you need cryptographic proof of client identity rather than (or in addition to) tokens.

Access logs and security groups

Access logs

Access logs capture detailed records of every request the load balancer processed — client IP, target, latencies, status codes, the matched rule, TLS cipher, and more — written to an S3 bucket you specify. They’re off by default and free to enable (you pay only S3 storage and the request charges). For an ALB they’re the primary forensic and analytics source: who hit what, how slow it was, which target served it, and whether the LB or the target returned an error. Turn them on in production; query them with Athena. (For real-time, there are also connection logs on the ALB and flow logs at the VPC level.)

# Enable ALB access logs to an S3 bucket (bucket policy must allow the ELB account)
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn "$ALB_ARN" \
  --attributes \
  Key=access_logs.s3.enabled,Value=true \
  Key=access_logs.s3.bucket,Value=my-alb-logs-bucket \
  Key=access_logs.s3.prefix,Value=prod-alb

Security groups — ALB vs NLB

This trips people up because the two behave differently.

Note also that a Network ACL sits at the subnet level and is stateless: it must allow both the inbound request and the outbound ephemeral-port response in each direction, or return traffic silently dies. See AWS Security Groups vs Network ACLs for that interaction.

AWS Elastic Load Balancing: ALB, NLB, GWLB

The diagram above maps the family onto the OSI stack — ALB at L7 reading paths and hosts into multiple target groups, the NLB at L4 hashing flows (with its optional static EIPs), and the GWLB at L3 steering packets through inspection appliances — with the shared listener → rules → target group → health-checked targets pipeline beneath them.

Going deeper

The core above is enough to design and pass the exam. This section is for the reader who runs load balancers in anger — the timing knobs, the hardening attributes, the exact shape of the bill, and the failure modes that only show up at scale.

Idle timeout, keep-alive, and the 502/504 boundary

Every load balancer holds a connection open only so long before it reclaims it. On the ALB this is the connection idle timeout — idle_timeout.timeout_seconds, default 60 s, adjustable 1–4000 s — and it applies to both the client→ALB and the ALB→target sides. If a target takes longer than the idle timeout to send the first byte of a response, the ALB gives up and returns 504 Gateway Timeout. Long operations (report generation, large uploads) therefore need a higher idle timeout, or an async pattern.

The subtler failure is the 502 Bad Gateway caused by a keep-alive race: the ALB reuses a pooled back-end connection, but the target had already decided to close it. The rule is simple and worth committing to memory — set the target’s own keep-alive timeout higher than the ALB idle timeout (e.g. target 75 s when the ALB is 60 s) so the ALB always closes the connection first. Many stock web-server defaults (nginx keepalive_timeout 65, older Node.js server.keepAliveTimeout of 5 s) get this backwards and produce sporadic 502s under load. A separate 2023 attribute, client_keep_alive.seconds (default 3600), caps the total lifetime of a client connection independent of idle time.

The NLB is different: a TCP flow’s idle timeout is 350 s and is not configurable, and UDP flows time out at 120 s. Because the NLB does not send a TCP reset when it reaps an idle flow, a client that goes quiet for longer than 350 s can find its connection silently dead. The fix is TCP keep-alives from the application (interval < 350 s) so the flow never looks idle. This is the single most common “my long-lived NLB connection dropped for no reason” cause.

Hardening attributes you should set on day one

Beyond security groups and TLS policy, a production load balancer has a set of attributes that are off or permissive by default and worth turning on deliberately:

Attribute Type Default Why you set it
deletion_protection.enabled LB false Blocks delete-load-balancer until you explicitly disable it — cheap insurance against a fat-fingered terraform destroy or console click on a prod LB.
routing.http.desync_mitigation_mode ALB defensive Guards against HTTP request smuggling / desync attacks. monitor only classifies and logs; defensive (default) mitigates ambiguous requests; strictest enforces strict RFC 9112 compliance and drops non-conforming requests. Raise to strictest for security-sensitive apps.
routing.http.drop_invalid_header_fields.enabled ALB false Strips headers with invalid characters before they reach your app — closes a class of header-injection bugs.
routing.http.xff_header_processing.mode ALB append Controls whether the ALB appends, preserves, or removes X-Forwarded-For. Set deliberately if you sit behind CloudFront and need the true client IP.
waf.fail_open.enabled ALB false If WAF is unreachable, false = fail closed (block). Choose consciously; failing open trades security for availability.
# Belt-and-braces: deletion protection + strict desync mitigation on a prod ALB
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn "$ALB_ARN" \
  --attributes \
    Key=deletion_protection.enabled,Value=true \
    Key=routing.http.desync_mitigation_mode,Value=strictest \
    Key=routing.http.drop_invalid_header_fields.enabled,Value=true

The LCU / NLCU pricing model, dimension by dimension

Every v2 load balancer bills two things: a small hourly rate for the load balancer itself, plus capacity units consumed. You do not add the dimensions up — for each hour you are charged on the single highest of them. Knowing which dimension dominates tells you where your cost actually comes from.

An ALB Load Balancer Capacity Unit (LCU) measures four dimensions; 1 LCU equals whichever of these is the ceiling that hour:

So a connection-storm API with tiny payloads bills on the new-connections axis, while a media service bills on processed bytes — the same LB, different dominant dimension. The NLB uses an analogous NLCU with new flows, active flows, and processed bytes (no rule evaluations), and TLS listeners are metered on a separate, higher-priced NLCU because the NLB is doing the crypto. The GWLB bills in GLCUs. Practical levers: consolidate small ALBs (each has its own hourly charge), keep rule counts lean, and remember that enabling cross-zone on an NLB adds inter-AZ data-transfer charges on top of the NLCU bytes — the one place the “cross-zone is free” intuition from the ALB does not carry over.

Surviving a bad AZ: zonal shift and zonal affinity

Both ALB and NLB integrate with Route 53 Application Recovery Controller (ARC) zonal shift: with one API call (or zonal autoshift, where AWS triggers it for you on detecting an impairment) you pull one AZ’s load-balancer IPs out of DNS, evacuating that zone without touching your targets. This is the fastest lever for a grey AZ failure — but it only helps if your other AZs have the capacity to absorb the load, which is the real argument for the “keep target counts balanced across AZs” rule from the cross-zone section.

The NLB adds a related knob, dns_record.client_routing_policy: the default any_availability_zone lets clients resolve to any AZ’s node, while availability_zone_affinity steers a client to the node in its own AZ to shave inter-AZ latency and data-transfer cost — at the price of concentrating a client’s blast radius in one zone. partial_availability_zone_affinity splits the difference.

Two operational gotchas: the access-log bucket policy and the SG-less NLB

Access logs need the load balancer’s delivery identity to have s3:PutObject on your bucket, and the identity differs by region generation. In older regions you must allow a numeric ELB account ID (each region has its own, e.g. 127311923021 for us-east-1) as the principal. In newer regions (roughly those launched from 2022 on) delivery uses the service principal logdelivery.elasticloadbalancing.amazonaws.com instead. Copy the wrong one for your region and logs silently never appear — there is no error, the toggle just does nothing. Second gotcha, restated because it is the classic one: an NLB can only gain a security group if you attach it at creation time — there is no “add SG later” for an NLB that was built without one; you must recreate it.

Service quotas that bite at scale

These are default, soft quotas — raise them through Service Quotas — but the defaults catch teams by surprise:

Quota Default Note
Rules per ALB 100 Excludes the default rule; heavy multi-tenant routing hits this.
Certificates per listener (SNI) 25 Adjustable; a SaaS fronting many customer domains will need a raise.
Listeners per load balancer 50 Per LB.
Targets per ALB 1,000 Per load balancer, across all target groups.
Targets per AZ per NLB 500 Per Availability Zone.
Target groups per region 3,000 Account-wide.

Provisioning it as code

In production you never click this together — you declare it. In Terraform (AWS provider v5) the canonical resources are aws_lb (with load_balancer_type = "application" | "network" | "gateway"), aws_lb_target_group, aws_lb_listener, and aws_lb_listener_rule. Cross-zone for a network LB is enable_cross_zone_load_balancing on the aws_lb (default false for NLB, matching AWS; ALB is always on and the argument is ignored). CloudFormation exposes the same objects as AWS::ElasticLoadBalancingV2::LoadBalancer, TargetGroup, Listener, and ListenerRule.

resource "aws_lb" "web" {
  name                       = "prod-web-alb"
  load_balancer_type         = "application"
  security_groups            = [aws_security_group.alb.id]
  subnets                    = var.public_subnet_ids
  enable_deletion_protection = true
  drop_invalid_header_fields = true
}

resource "aws_lb_target_group" "web" {
  name        = "prod-web-tg"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = var.vpc_id
  target_type = "instance"

  # Least-outstanding-requests suits non-uniform microservice traffic
  load_balancing_algorithm_type = "least_outstanding_requests"

  health_check {
    path                = "/healthz"
    matcher             = "200-299"
    healthy_threshold   = 3
    unhealthy_threshold = 2
    interval            = 15
  }
}

Hands-on lab

We’ll stand up an internet-facing ALB in front of a tiny two-instance target group, watch health checks turn the targets healthy, hit the DNS name, add a path-based rule, and clean everything up. This uses small t3.micro instances and an ALB; the ALB has an hourly + LCU charge, so we tear it down at the end. Run it in a non-production account, in a region with at least two AZs.

0. Set variables (replace the VPC and two subnet IDs from two different AZs).

REGION=eu-west-1
VPC_ID=vpc-xxxxxxxx
SUBNET_A=subnet-aaaaaaa   # AZ a
SUBNET_B=subnet-bbbbbbb   # AZ b

1. Create a security group for the ALB (allow HTTP from the world).

ALB_SG=$(aws ec2 create-security-group --group-name lab-alb-sg \
  --description "ALB SG" --vpc-id $VPC_ID --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

2. Create a security group for the instances that allows port 80 from the ALB’s SG.

WEB_SG=$(aws ec2 create-security-group --group-name lab-web-sg \
  --description "web SG" --vpc-id $VPC_ID --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $WEB_SG \
  --protocol tcp --port 80 --source-group $ALB_SG

3. Launch two t3.micro instances (Amazon Linux 2023) running a one-line web server via user data — one per subnet. Look up the latest AL2023 AMI from SSM:

AMI=$(aws ssm get-parameters --names \
  /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
  --query 'Parameters[0].Value' --output text)

USERDATA=$(printf '#!/bin/bash\ndnf -y install httpd\necho "Hello from $(hostname -f)" > /var/www/html/index.html\nsystemctl enable --now httpd\n' | base64)

for SUBNET in $SUBNET_A $SUBNET_B; do
  aws ec2 run-instances --image-id $AMI --instance-type t3.micro \
    --security-group-ids $WEB_SG --subnet-id $SUBNET \
    --user-data "$USERDATA" \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=lab-web}]'
done

Wait until both instances are running, then capture their IDs:

INSTANCE_IDS=$(aws ec2 describe-instances \
  --filters Name=tag:Name,Values=lab-web Name=instance-state-name,Values=running \
  --query 'Reservations[].Instances[].InstanceId' --output text)
echo $INSTANCE_IDS

4. Create a target group (instance type, HTTP:80, health check /).

TG_ARN=$(aws elbv2 create-target-group --name lab-tg --protocol HTTP --port 80 \
  --vpc-id $VPC_ID --target-type instance \
  --health-check-path / --matcher HttpCode=200 \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

for ID in $INSTANCE_IDS; do
  aws elbv2 register-targets --target-group-arn $TG_ARN --targets Id=$ID
done

5. Create the ALB across both subnets and a listener forwarding to the target group.

ALB_ARN=$(aws elbv2 create-load-balancer --name lab-alb \
  --type application --scheme internet-facing \
  --subnets $SUBNET_A $SUBNET_B --security-groups $ALB_SG \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)

aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
  --protocol HTTP --port 80 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

6. Validate — watch health, then curl the DNS name.

# Targets should move from "initial" to "healthy" within ~1-2 minutes
aws elbv2 describe-target-health --target-group-arn $TG_ARN \
  --query 'TargetHealthDescriptions[].{id:Target.Id,state:TargetHealth.State}' \
  --output table

DNS=$(aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN \
  --query 'LoadBalancers[0].DNSName' --output text)
echo "http://$DNS"

# Repeat a few times — you should see responses from BOTH hostnames (ALB spreads load)
for i in 1 2 3 4 5 6; do curl -s "http://$DNS"; done

Expected: describe-target-health shows both targets healthy, and the curl loop returns “Hello from …” alternating between the two instance hostnames — proof the ALB is load-balancing across AZs (cross-zone is on by default for ALB).

7. (Optional) Add a path-based rule that returns a fixed maintenance response for /maintenance:

LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN \
  --query 'Listeners[0].ListenerArn' --output text)

aws elbv2 create-rule --listener-arn $LISTENER_ARN --priority 10 \
  --conditions Field=path-pattern,Values='/maintenance' \
  --actions 'Type=fixed-response,FixedResponseConfig={StatusCode=503,ContentType=text/plain,MessageBody=Down for maintenance}'

curl -s "http://$DNS/maintenance"   # -> "Down for maintenance"

Cleanup — delete in dependency order (listener/rules go with the LB):

aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN
sleep 30
aws elbv2 delete-target-group --target-group-arn $TG_ARN
aws ec2 terminate-instances --instance-ids $INSTANCE_IDS
aws ec2 wait instance-terminated --instance-ids $INSTANCE_IDS
aws ec2 delete-security-group --group-id $WEB_SG
aws ec2 delete-security-group --group-id $ALB_SG

Cost note: an ALB is not free-tier-free in the way EC2 micro is — it bills an hourly rate plus Load Balancer Capacity Units (LCUs), together roughly US$0.02–0.03 per hour in most regions, so an hour of lab time costs a few cents; the two t3.micro instances fall under the EC2 free tier for the first 12 months (750 hrs/month combined) or cost a couple of cents each per hour otherwise. Delete the ALB promptly — an idle ALB still bills its hourly charge 24×7.

Common mistakes & troubleshooting

Symptom Likely cause Fix
All targets unhealthy, ALB returns 503 Target SG doesn’t allow the ALB SG on the health-check port, or /healthz returns a non-matching code Allow the ALB’s SG inbound to the target on the traffic and health-check ports; align the matcher with what /healthz actually returns.
502 Bad Gateway from the ALB Target closed the connection, returned a malformed/oversized response, or app crashed Check the app logs and access logs; verify keep-alive timeout on the target ≥ the ALB idle timeout (default 60s).
504 Gateway Timeout Target took longer than the ALB idle timeout to respond Raise the idle timeout, or speed up the backend; long uploads need a higher timeout.
Uneven load: one instance hammered Cross-zone off (NLB/GWLB default) with unequal target counts per AZ Balance targets across AZs (ASG rebalancing) or enable cross-zone (mind NLB inter-AZ cost).
NLB targets reject traffic, SG looks correct NLB preserves client source IP; the target SG allows the NLB/VPC range, not the real client Allow the client CIDRs, or the VPC CIDR, on the target SG — reason from source-IP preservation.
New deploys drop in-flight requests Deregistration delay too low, or no draining tie-in to ASG lifecycle hook Set a sensible deregistration_delay.timeout_seconds; add an ASG lifecycle hook to drain before terminate.
Wrong certificate served on a multi-domain ALB No SNI certificate for that host; client falls back to the default cert Add the per-host certificate via add-listener-certificates; verify the SNI hostname matches the cert SAN.
Lambda target returns errors behind ALB Response not in the ALB’s required JSON shape, or Lambda perms missing Return {statusCode, headers, body, isBase64Encoded}; grant the ALB lambda:InvokeFunction.

Common beginner mistakes

These are misconceptions rather than symptoms — the wrong mental model that leads you into the troubleshooting table above. Fix the model and the bug never happens.

Best practices

Security notes

Interview & exam questions

  1. What’s the core difference between an ALB and an NLB, and how do you choose? The ALB is a Layer-7 load balancer that understands HTTP and routes on content (host, path, headers, method); the NLB is a Layer-4 load balancer that forwards TCP/UDP flows with ultra-low latency, supports static Elastic IPs, and preserves the client source IP. Choose the ALB for web apps and content-based routing; the NLB for extreme performance, non-HTTP protocols, static IPs, or PrivateLink fronts.

  2. Why does my application read the load balancer’s IP as the source address, and how do I get the real client IP? An ALB terminates the connection and opens a new one to the target, so the target sees the ALB’s IP; the original client IP is in the X-Forwarded-For header. An NLB with instance/IP targets preserves the client source IP at the TCP layer by default, so the target sees the real client IP directly.

  3. Explain cross-zone load balancing, its defaults, and its cost implication. Cross-zone lets a node in one AZ send traffic to targets in all AZs (even distribution) rather than only its own (per-AZ-then-per-target distribution, which is uneven if AZ target counts differ). It’s on by default and free for the ALB, and off by default for the NLB/GWLB, where enabling it incurs inter-AZ data-transfer charges.

  4. A user reports their session keeps “logging out”. How might the load balancer be involved, and how do you fix it properly? If the app holds session state in memory and stickiness is off, requests land on different targets that don’t share state. The quick fix is stickiness (duration cookie or app cookie on the ALB); the proper fix is stateless backends with session state in ElastiCache/DynamoDB so any target can serve any request.

  5. All your ALB targets are unhealthy and clients get 503. Walk through the diagnosis. 503 means no healthy targets. Check the target SG allows the ALB SG inbound on the traffic and health-check ports; confirm the health-check path/port/matcher match a real endpoint and code; verify the app is actually listening; check the NACL allows return traffic. Use describe-target-health for the per-target reason code.

  6. What target types can an ALB target group contain, and what’s special about the alb type? instance, IP, and Lambda (ALB only). The alb target type belongs to an NLB target group and points at an ALB, so you can put an NLB (static EIPs, PrivateLink) in front of an ALB’s L7 routing.

  7. What is SNI and why does it matter on an ALB? Server Name Indication lets one HTTPS listener serve multiple certificates and present the correct one based on the hostname in the TLS handshake, so a single ALB can front many domains each with its own certificate — no load balancer per domain.

  8. What is deregistration delay / connection draining, and what value would you set? It’s the grace period during which a deregistering target receives no new requests but finishes in-flight ones (default 300s). Lower it (e.g. 30s) for short, stateless requests to speed deploys; raise it for long uploads or long-lived connections — and tie it to ASG lifecycle hooks.

  9. When would you reach for a Gateway Load Balancer? To insert third-party security appliances (firewall, IDS/IPS) transparently into the traffic path at Layer 3, using GENEVE encapsulation and GWLB endpoints, while preserving flow stickiness — the AWS-native pattern for centralised appliance-based inspection.

  10. Does an NLB have a security group? Historically no — you secured targets against client/VPC CIDRs (because the NLB preserves the source IP). Since August 2023 an NLB can have a security group, but it must be enabled at creation; an SG-less NLB cannot gain one later.

  11. What is mTLS on an ALB and what are the two modes? Mutual TLS makes the client present a certificate that the ALB validates. verify checks the client cert against an uploaded trust store (CA bundle in S3) with optional CRL; passthrough forwards the client certificate chain to your app to validate. Used for strong client authentication.

  12. What is slow start and which load balancers support it? Slow start ramps a newly healthy target’s traffic linearly from zero to full over 30–900s so cold instances aren’t overwhelmed on join. It applies to ALB and CLB target groups (not NLB) and only when the group has other targets to share with.

Quick check

  1. Which load balancer operates at Layer 7 and can route on URL path and host header?
  2. Which load balancer can be assigned a static Elastic IP per Availability Zone?
  3. What is the default value of deregistration_delay.timeout_seconds?
  4. Cross-zone load balancing is on-by-default and free for which load-balancer type?
  5. What ALB feature lets one HTTPS listener serve certificates for many different domains?

Answers

  1. The Application Load Balancer (ALB).
  2. The Network Load Balancer (NLB) — one Elastic IP per AZ.
  3. 300 seconds (5 minutes).
  4. The ALB (cross-zone is always on for ALB and incurs no inter-AZ charge; it’s off-by-default and chargeable on the NLB/GWLB).
  5. SNI (Server Name Indication) with multiple listener certificates.

Exercise

Design and build, using the CLI, an internet-facing ALB that serves two microservices behind one HTTPS listener: requests to api.<yourdomain> path /v1/* go to a target group tg-api, and everything else goes to tg-web. Requirements: (1) an HTTP:80 listener that redirects to HTTPS; (2) an HTTPS:443 listener using an ACM certificate and a TLS 1.3 security policy; (3) a path-based rule for /v1/*; (4) access logs enabled to an S3 bucket; (5) health checks on a dedicated /healthz path with a 200-299 matcher. Then deliberately break it: tighten the tg-api target SG to remove the ALB SG rule and observe the targets go unhealthy and the listener return 503 — confirming you understand the SG chain. Restore it, verify both routes, and tear everything down. Bonus: put an NLB with a static EIP in front of the ALB using the alb target type and curl the NLB’s EIP.

Practice challenges

Six graded exercises, beginner to advanced. Try each before opening the solution. They use the same $ALB_ARN, $LISTENER_ARN, $TG_ARN placeholders as the lab; nothing here needs a live account to reason through.

1. (Beginner) Pick the load balancer. For each requirement, name the load balancer: (a) a partner must allowlist a fixed IP for your endpoint; (b) route /api/* and /static/* to different backends; © insert a third-party firewall appliance inline for packet inspection.

<details><summary>Solution</summary>

(a) NLB — the only ELB with static Elastic IPs. (b) ALB — Layer-7 path-based routing. © GWLB — transparent appliance insertion via GENEVE.

Why: the choice falls straight out of the layer and the one distinctive feature each type owns (static IP → NLB, content routing → ALB, appliances → GWLB). </details>

2. (Beginner) Redirect HTTP to HTTPS. Write the CLI to add an HTTP:80 listener whose default action is a permanent redirect to HTTPS:443.

<details><summary>Solution</summary>

aws elbv2 create-listener \
  --load-balancer-arn "$ALB_ARN" \
  --protocol HTTP --port 80 \
  --default-actions \
  'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'

Why: HTTP_301 is a permanent redirect so browsers and search engines cache the HTTPS location; the ALB does the redirect itself, so no backend is involved. </details>

3. (Intermediate) Header-based routing. On an existing HTTPS listener, add a rule at priority 20 that forwards requests carrying the header X-Tenant: acme to a target group $TG_ACME.

<details><summary>Solution</summary>

aws elbv2 create-rule \
  --listener-arn "$LISTENER_ARN" \
  --priority 20 \
  --conditions 'Field=http-header,HttpHeaderConfig={HttpHeaderName=X-Tenant,Values=[acme]}' \
  --actions Type=forward,TargetGroupArn="$TG_ACME"

Why: http-header conditions are how ALBs do multi-tenant and canary routing; lower priority numbers are evaluated first, and the first fully-matching rule wins. </details>

4. (Intermediate) Tune a target group for fast, fair deploys. Change $TG_ARN to route by least outstanding requests and drop its deregistration delay to 30 s.

<details><summary>Solution</summary>

aws elbv2 modify-target-group-attributes \
  --target-group-arn "$TG_ARN" \
  --attributes \
    Key=load_balancing.algorithm.type,Value=least_outstanding_requests \
    Key=deregistration_delay.timeout_seconds,Value=30

Why: LOR keeps a slow target from accumulating work, and 30 s draining is plenty for short, stateless requests — deploys finish fast instead of waiting out the 300 s default. </details>

5. (Advanced) Static IP in front of Layer-7 routing. A partner needs a static IP allowlist but your service needs the ALB’s path routing. Describe the construct, then write the CLI to create the NLB target group and register the ALB.

<details><summary>Solution</summary>

Put an NLB in front of the ALB using the alb target type: the NLB gives static Elastic IPs, the ALB behind it keeps the L7 rules.

# NLB target group of type 'alb', then register the ALB as its target
TG_ALB=$(aws elbv2 create-target-group --name front-alb-tg \
  --protocol TCP --port 443 --vpc-id "$VPC_ID" --target-type alb \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

aws elbv2 register-targets --target-group-arn "$TG_ALB" \
  --targets Id="$ALB_ARN"
# then create a TCP:443 listener on the NLB that forwards to $TG_ALB

Why: the alb target type belongs to an NLB target group and points at an ALB — the canonical way to expose L7 routing behind a static IP or an AWS PrivateLink endpoint service. </details>

6. (Advanced) Diagnose the “SG looks right but NLB targets reject traffic.” Instance targets behind an NLB refuse connections even though the target SG allows the NLB’s security group and the VPC CIDR. What’s wrong, what’s the fix, and what one-time constraint applies to NLB security groups?

<details><summary>Solution</summary>

The NLB has client-IP preservation on (default for instance/IP targets), so packets arrive with the client’s source IP — not the NLB’s. A target SG that only allows the NLB or VPC range drops them. Fix: allow the client CIDRs (or, for internal traffic, the VPC CIDR that the clients actually come from) on the target SG. Constraint: an NLB can only have a security group if it was attached at creation — an SG-less NLB cannot gain one later; you’d recreate the NLB.

Why: you must reason SG rules from what source IP the target sees, and source-IP preservation changes that from the LB’s IP to the real client’s. </details>

Certification mapping

Glossary

Next steps

AWSElastic Load BalancingALBNLBNetworkingTarget Groups
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments