Skip to content

Load balancer and target groups

2 min read · main part only; folded sections are optional

After this you can answer: How does the load balancer pick a task, and what makes a target unhealthy?

flowchart TB
B[Browser] -->|"HTTPS 443"| L["listener :443<br/>ACM certificate"]
L -->|"rule 100: /trpc/*"| ATG["api target group<br/>port 4000, GET /health"]
L -->|"default"| WTG["web target group<br/>port 3000, GET /"]
ATG --> A["api task"]
WTG --> W["web task"]
  1. The listener ends TLS. On port 443 the ALB decrypts HTTPS with our certificate. Inside the VPC it talks plain HTTP to the tasks.
  2. Rules are checked in priority order. Rule 100 matches /trpc/*; anything else falls to the default action, the web target group.
  3. The target group lists targets by IP. Fargate tasks have their own IPs; the ECS service registers each new task and removes stopped ones.
  4. Health checks decide. Every 15 s the ALB calls each target. 2 passes make it healthy, 3 failures (or no answer within 5 s) make it unhealthy, and it gets no more requests.

The ALB minitickets-dev is internet-facing, sits in both public subnets and uses alb-sg (infra/src/entry/alb.ts → createAlb). Port 80 answers every request with a 301 redirect to https://. The TLS policy allows only TLS 1.2 and 1.3.

The api health check calls /health, not /ready: /health only says the process is up, so a short database hiccup does not take every api task out at once. A stopping task gets 30 s (deregistrationDelay) to finish requests in flight (infra/src/entry/target-groups.ts → createTargetGroups).

The ALB is billed every hour it exists, which is why it sits behind the entryEnabled switch.

From → toWhat happensWhat allows it
Application Load Balancer → Public subnetsonly when entryEnabled = trueone ALB node in each public subnet
  • config internal: false, subnets = both public subnetscreateAlb
Browser → Application Load Balanceronly when cdnEnabled = falseHTTPS on port 443; plain HTTP on port 80 only gets a redirect
  • route public route table: 0.0.0.0/0 → minitickets-dev-igw. The task has a public IP; there is no NAT gateway and no VPC endpoint.createNetwork
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-443-0: TCP 443 from allowedCidrs (default 0.0.0.0/0)createAlbPublicIngress
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-80-0: TCP 80 from allowedCidrs, for the 301 redirect onlycreateAlbPublicIngress
  • certificate the :443 listener presents the ACM certificate for minitickets.theversehub.increateHttpsListener
Application Load Balancer → HTTPS listeneronly when cdnEnabled = falsethe ALB hands each request to the listener for its port
  • listener-rule listener :443 HTTPS, TLS policy ELBSecurityPolicy-TLS13-1-2-2021-06; listener :80 HTTP answers 301 → https://createHttpsListener
CloudFront distribution → Application Load Balanceronly when cdnEnabled = truefetches from the origin over plain HTTP on port 80, adding the X-Origin-Verify header
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-80: TCP 80 from the AWS managed prefix list com.amazonaws.global.cloudfront.origin-facingcreateSecurityGroups
  • secret origin customHeaders: X-Origin-Verify = the origin secretcreateCdn
Application Load Balancer → HTTP listener with the origin headeronly when cdnEnabled = truethe ALB hands each request to its :80 listener
  • listener-rule listener :80 HTTP, default action: fixed response 403 ForbiddencreateListener
Application Load Balancer → ALB security grouponly when entryEnabled = truethe ALB's network interfaces use the ALB security group
Application Load Balancer → CloudWatch alarmsonly when entryEnabled = truetarget 5xx + ALB 5xx ≥ 5 in 5 minutes → alb-5xx
  • metric AWS/ApplicationELB FILL(HTTPCode_Target_5XX_Count, 0) + FILL(HTTPCode_ELB_5XX_Count, 0), period 300 salarmSpecs
See the ALB on the map →

EC2 → Target groups → minitickets-dev-api-tg-… → Targets: Health status and Health status details.

SymptomCauseWhere to look
Every request returns 503The target group has no healthy targetTarget group → Targets
Targets unhealthy: Request timed outapi-sg does not allow 4000 from alb-sgHealth status details
/trpc/* calls get the Next.js 404 pageRule 100 is missingListener → Rules
What is the difference between a 502 and a 503 from the ALB?

503: no healthy target to send to. 502: a target was picked but closed the connection or sent a broken answer, for example a task dying during a Spot interruption.

Why not check /ready, which also pings the database?

If Postgres had a short hiccup, every api task would fail the check at once and the ALB would have nobody to send to. With /health, the tasks stay and answer what they can.

ALB or Network Load Balancer?

An ALB understands HTTP: paths, headers, redirects, TLS with ACM. A Network Load Balancer works on TCP and is faster for raw connections, but cannot route by path. We need path routing.

Advanced: Listener and target group details

TLS policy ELBSecurityPolicy-TLS13-1-2-2021-06. dropInvalidHeaderFields removes invalid HTTP header-name fields before forwarding. Target groups have no fixed name (a random suffix), because a change like a new port replaces the group and two groups cannot share a name. In CloudFront mode the listener is different: see the CloudFront chapter.

Answer these without scrolling up. If one feels hard, reread that section.

  1. Where does TLS end, and how do the ALB and the tasks talk after that?
  2. Which rule sends a request to the api, and what happens to every other path?
  3. Who puts task IPs into a target group?
  4. What makes a target unhealthy?
  5. Why does the api health check use /health instead of /ready?

Redraw it from memory: browser → listener :443 → rule 100 to the api target group, default to the web target group → tasks

Try this: on the map, select the HTTPS listener and read its rule and default action in the Connections tab

Read this book online: https://book.theversehub.in/