Load balancers
One address in front of many servers.
Part 1: The problem
The QuickCart app talked to its API server using the server’s public IP address. One Friday the server was stopped and started to change its size. It came back with a new IP. Every copy of the app in the world was now calling an address that went nowhere, until a new app release went out.
The next weekend was busy. One server couldn’t keep up, so Priya launched a second. But the app only knew one address, so the second server sat idle while the first one struggled.
QuickCart needs one address that never changes, in front of however many servers it runs, and something that stops sending customers to a server that has broken.
Part 2: The idea
A load balancer is one fixed address in front of many servers. Customers only ever talk to it. It passes each request on to one of the servers behind it, and it keeps checking which of those servers are healthy.
Servers can come and go, change size and change address. The address customers use never changes.
The pieces
AWS’s load balancer for websites and APIs is the Application Load Balancer, or ALB. It’s made of a few parts that you configure separately:
| Piece | What it is | QuickCart’s |
|---|---|---|
| Load balancer | The front door. It has a DNS name and sits in a public subnet in each zone | quickcart-alb, in two zones |
| Listener | A port and protocol it listens on, and what to do with requests that arrive | HTTP on 80, HTTPS on 443 |
| Rule | A condition on a listener, like “the path starts with /orders”, and which target group gets those requests | Everything goes to one target group, for now |
| Target group | A list of servers, plus the health check that decides which of them get traffic | quickcart-web-tg |
| Target | One server in the group | Each API server |
A request’s path: customer → listener → rule → target group → one healthy target.
Three kinds of load balancer
| Type | Understands | Use it for |
|---|---|---|
| Application (ALB) | HTTP: paths, hostnames, headers | Websites and APIs. QuickCart’s choice |
| Network (NLB) | Only TCP and UDP connections, not what’s in them | Very high traffic, non-HTTP protocols, fixed IP addresses |
| Gateway | Network packets, sent through a security appliance | Putting firewalls in the path. You’ll rarely need it |
Health checks: who gets traffic
Every few seconds, the load balancer sends each target a small request, like GET /health. If the answer is right, the check passes. If it’s wrong, slow or missing, it fails.
| Setting | What it means | Default | QuickCart |
|---|---|---|---|
| Path | Which URL to ask | / | /health |
| Interval | How often to ask | 30 seconds | 10 seconds |
| Timeout | How long to wait for an answer. Must be less than the interval | 5 seconds | 5 seconds |
| Unhealthy threshold | Failures in a row before the target gets no traffic | 2 | 2 |
| Healthy threshold | Passes in a row before it gets traffic again | 5 | 2 |
| Success codes | Which HTTP status counts as a pass | 200 | 200 |
How long before a broken server stops getting traffic? Interval × unhealthy threshold. For QuickCart that’s 10 × 2 = about 20 seconds. Requests that reach the broken server in that window fail. Checking every second would catch it sooner, but a single slow answer would then pull a healthy server out.
Shallow or deep?
| Check | What /health does | When the database fails |
|---|---|---|
| Shallow | Answers 200 if the app is running. Touches nothing else | Every server stays in. Pages that need the database show an error. Degraded, not down |
| Deep | Also queries the database, the cache, other services | Every server fails at once. They’re all taken out — and with auto scaling, all replaced, and the replacements fail too. Down |
QuickCart tested this. They removed the security group rule that let the servers reach the database. /health kept answering 200, and / answered 500. The site was degraded, not down: the servers stayed in, showed a clear error, and recovered the moment the rule was put back. An alarm on the error rate told a person what was wrong. A deep check would have failed all the servers together and turned a database problem into a total outage.
So database problems are a job for alarms, which tell a person. Health checks are for one broken server, which a machine can fix by replacing it.
Taking a server out gently: deregistration delay
When a target is removed on purpose — during a release, or when auto scaling shrinks the group — some requests to it may still be in progress. The deregistration delay stops new requests going to the target, but waits for those in progress to finish, up to a limit.
The default is 300 seconds. That’s safe, but it makes every release wait five minutes per batch. QuickCart’s API answers in well under a second, so 30 seconds is plenty. This is also called connection draining.
Status codes that come from the load balancer
When something goes wrong, the code tells you where to look:
| Code | Means | Usually |
|---|---|---|
| 502 Bad Gateway | A target was chosen, but the connection failed or the answer was broken | The app crashed or closed the connection |
| 503 Service Unavailable | There was no target to send it to | The target group is empty |
| 504 Gateway Timeout | The target took too long to answer | A slow query, or a security group dropping the traffic |
HTTPS, and sending HTTP to it
The ALB can do the HTTPS work itself, so the servers behind it don’t have to. You add a listener on port 443, attach a certificate from AWS Certificate Manager (free for use with a load balancer), and forward to the target group.
Then the listener on port 80 changes from forward to redirect: send the browser to the same address with https://, with status 301, which means “moved for good”. Nobody who types the plain address gets an error, and nothing travels unencrypted. Domain names and HTTPS walks through getting the certificate.
Only the load balancer may talk to the servers
The servers shouldn’t accept traffic from the internet directly. Their security group allows port 80 only from the load balancer’s security group, not from an IP range. Security groups explains this chain.
| Security group | Allows in |
|---|---|
| Load balancer’s | 80 and 443 from anywhere |
| Servers’ | 80 from the load balancer’s security group only |
An ALB always spreads requests across targets in every zone it’s in, so if one zone loses servers, the others take all the traffic. By default it takes turns, called round robin. The least outstanding requests setting instead sends each request to the target with the fewest in progress, which helps when some requests are much slower than others.
Part 3: See it
Three servers sit behind QuickCart’s load balancer, in an auto scaling group that replaces unhealthy ones. Send some requests and watch them spread. Then crash the app on one server and send again. Finally, break the database, and compare a shallow health check with a deep one.
200 OK: 0500 error: 0502 bad gateway: 0
What to notice: with the database broken and a shallow check, every request failed — but every server stayed in and answered. Switch to a deep check, and the same problem took out every server at once. Nothing about the servers changed. Only the question the health check asked.
Part 4: Do it
Costs money — about $1.75 a day while it runs — delete it after. The load balancer is about $0.0225 an hour plus a small usage charge (LCUs), each t3.micro is about $0.0104 an hour, and every public IPv4 address is about $0.005 an hour. These are us-east-1 prices; they change, so check the pricing pages. An hour of this lab costs well under 10 cents.
You’ll put two servers in two zones behind an Application Load Balancer, watch it take turns between them, then break one and watch the health check take it out. About 40 minutes.
You need the quickcart-ec2-ssm role from Servers on EC2. If you deleted it, create it again as in that lesson’s first step. Check the region selector says N. Virginia (us-east-1).
Create the load balancer's security group
In the AWS console: EC2Security GroupsCreate security group
Field Enter Security group name quickcart-alb-sgDescription QuickCart load balancer - HTTP from anywhereVPC The default VPC Under Inbound rules, choose Add rule:
Type Source HTTP Anywhere-IPv4 ( 0.0.0.0/0)Leave the outbound rule as it is, and choose Create security group.
This lab stays on plain HTTP, because a trusted HTTPS certificate needs a domain name. Domain names and HTTPS adds the 443 listener and the redirect.
Create the servers' security group
In the AWS console: EC2Security GroupsCreate security group
Field Enter Security group name quickcart-app-sgDescription QuickCart servers - HTTP from the load balancer onlyVPC The default VPC Under Inbound rules, choose Add rule:
Type Source HTTP Custom, then start typing quickcart-alb-sgand pick itLeave the outbound rule, and choose Create security group.
You should seeOne inbound rule on port 80 whose source is a security group ID starting
sg-, not an IP range. Only things inquickcart-alb-sgcan reach these servers.Launch two servers, one in each zone
In the AWS console: EC2InstancesLaunch instances
Field Enter Name quickcart-web-aApplication and OS Images Amazon Linux 2023 AMI, 64-bit (x86) Instance type t3.microKey pair (login) Proceed without a key pair Under Network settings, choose Edit:
Field Enter VPC The default VPC Subnet The default subnet in us-east-1a Auto-assign public IP Enable — so the server can reach Systems Manager and download nginx Firewall (security groups) Select existing security group, then quickcart-app-sgUnder Advanced details:
Field Enter IAM instance profile quickcart-ec2-ssmMetadata version V2 only (token required) User data The script below user-data.sh#!/bin/bash dnf install -y nginx hostname > /usr/share/nginx/html/index.html echo ok > /usr/share/nginx/html/health systemctl enable --now nginxLine What it does #!/bin/bashRun this as a bash script dnf install -y nginxInstall the nginx web server, answering yes automatically hostname > /usr/share/nginx/html/index.htmlMake the home page the server’s own name, so you can see which server answered echo ok > /usr/share/nginx/html/healthCreate a page at /healththat just saysok. This is the shallow health check: if nginx can answer, it passessystemctl enable --now nginxStart nginx now, and on every boot Choose Launch instance. Then launch a second one the same way, with only two differences:
Field Enter Name quickcart-web-bSubnet The default subnet in us-east-1b You should seeTwo instances,
quickcart-web-ain us-east-1a andquickcart-web-bin us-east-1b, both Running.Create the target group and its health check
In the AWS console: EC2Target groupsCreate target group
Field Enter Choose a target type Instances Target group name quickcart-web-tgProtocol : Port HTTP : 80IP address type IPv4 VPC The default VPC Protocol version HTTP1 Health check protocol HTTP Health check path /healthOpen Advanced health check settings:
Field Enter Health check port Traffic port Healthy threshold 2Unhealthy threshold 2Timeout 5secondsInterval 10secondsSuccess codes 200Choose Next. Tick both
quickcart-web-aandquickcart-web-b, check the port says80, and choose Include as pending below. Then choose Create target group.Now shorten the deregistration delay. Open
quickcart-web-tg, go to the Attributes tab, and choose Edit:Field Enter Deregistration delay 30secondsChoose Save changes.
You should seeOn the Targets tab, both instances with health status Unused, and a note that the target group isn’t used by any load balancer yet. Health checks only start once a load balancer uses the group.
Create the load balancer
In the AWS console: EC2Load balancersCreate load balancer
Under Application Load Balancer, choose Create.
Field Enter Load balancer name quickcart-albScheme Internet-facing Load balancer IP address type IPv4 VPC The default VPC Availability Zones and subnets Tick us-east-1a and us-east-1b, each with its default subnet Security groups quickcart-alb-sg. Remove thedefaultgroupUnder Listeners and routing:
Protocol Port Default action HTTP 80Forward to quickcart-web-tgChoose Create load balancer.
You should seequickcart-albwith state Provisioning. After a few minutes it becomes Active. On its Details tab is a DNS name ending inelb.amazonaws.com. Copy it.Check both targets are healthy
In the AWS console: EC2Target groupsquickcart-web-tgTargets
You should seeBoth targets go from Initial to Healthy within about half a minute.
Watch it take turns
In a terminal on your computer, ask the load balancer for its home page. Replace
ALB_DNS_NAMEwith the DNS name you copied:Terminalcurl --connect-timeout 10 http://ALB_DNS_NAME/Part What it means curlmake a web request and print the answer --connect-timeout 10give up after 10 seconds if nothing answers, instead of waiting over a minute http://ALB_DNS_NAME/the load balancer’s address. Paste yours in place of ALB_DNS_NAMEYou should seeip-172-31-20-15.ec2.internalThat’s the name of the server that answered. Run the same command four or five more times.
You should seeThe answer switches between two names: one for each server. Your names will differ, but each is the server’s private address written with dashes. Match them to your instances in the console’s Private IPv4 addresses column.
Break one server and watch it drop out
In the AWS console: EC2Instancesquickcart-web-aConnectSession Manager
Choose Connect, and in the terminal stop the web server:
Terminalsudo systemctl stop nginxPart What it means sudorun as root systemctl stopstop a running service nginxthe web server, and with it the /healthpageNow go back to Target groups → quickcart-web-tg → Targets and refresh.
You should seeWithin about 20 seconds — two failed checks, 10 seconds apart —
quickcart-web-ashows Unhealthy, with a reason like Health checks failed.Run your
curlcommand a few more times from your computer.You should seeEvery answer now comes from the same server: the one in us-east-1b. The load balancer only sends traffic to targets that pass their checks. If you were quick enough to run
curlduring those first 20 seconds, you may also have seen a502 Bad Gateway: a request that went to the broken server before the checks caught up.Bring it back:
Terminalsudo systemctl start nginxPart What it means systemctl startstart the service again nginxthe web server You should seeAfter two passing checks, about 20 seconds,
quickcart-web-ais Healthy again, andcurlswitches between both names once more.Clean-up step: Clean up
Delete things in this order. The load balancer first, because it’s the most expensive, and because the security groups can’t be deleted while it uses them.
In the AWS console: EC2Load balancersquickcart-albActionsDelete load balancer
Type
confirmand choose Delete.In the AWS console: EC2Target groupsquickcart-web-tgActionsDelete
Choose Yes, delete.
In the AWS console: EC2Instancesquickcart-web-a and quickcart-web-bInstance stateTerminate instance
Tick both instances first. Newer consoles call it Terminate (delete) instance. Confirm it.
In the AWS console: EC2Security Groupsquickcart-app-sgActionsDelete security groups
Delete
quickcart-app-sgfirst, thenquickcart-alb-sg: the first one refers to the second.Keep the
quickcart-ec2-ssmrole for the next lesson; roles are free.You should seeNo load balancers, no target groups, both instances Terminated, and neither security group left. Nothing is billing.
Part 5: Check yourself
What decides which servers a load balancer sends requests to?
QuickCart's database goes down. With a shallow /health check, what do customers see?
Why can a deep health check, one that also queries the database, cause a total outage?
The health check interval is 10 seconds and the unhealthy threshold is 2. A server's app crashes. Roughly how long until it stops getting traffic?
What does the deregistration delay do?
What should QuickCart's port 80 listener do once HTTPS is set up?
Part 6: Cheat sheet
| Term | In one line |
|---|---|
| Load balancer | One fixed address in front of many servers |
| ALB | For HTTP: can route by path and hostname, and do HTTPS |
| Listener | A port and protocol, and what to do with requests on it |
| Target group | The servers, plus the health check that picks who gets traffic |
| Health check | A small request every few seconds. Fail enough in a row: no traffic |
| Time to take a broken server out | Interval × unhealthy threshold. 10 s × 2 = about 20 s |
| Shallow check | “Is this server broken?” — use this |
| Deep check | “Is everything behind it working?” — one shared failure takes out every server |
| Deregistration delay | Lets in-progress requests finish. Default 300 s; QuickCart uses 30 s |
| 502 / 503 / 504 | Broken answer / no target / too slow |
| Port 80 listener | Redirect to HTTPS, 301 |
| Servers’ security group | Port 80 from the load balancer’s security group only |
| Timed out? | Check the security groups first |