Set Up an ALB with EC2
Learn to set up an application load balancer with EC2 in this AWS Cloud & DevOps with Python tutorial — hands-on steps, troubleshooting, and what to study next.
Focus: set up an application load balancer with ec2
You've built your EC2 instances, automated them with Python, and pointed users to a single public IP — but what happens when traffic spikes, one of those instances fails, or a new deployment needs zero-downtime rollouts? Requests start timing out, users see 502 errors, and your sleep schedule evaporates. This lesson solves that pain by teaching you to set up an Application Load Balancer (ALB) with EC2 — the AWS service that distributes incoming traffic across multiple instances, performs health checks automatically, and keeps your Python web app highly available without manual intervention.
The problem this lesson solves
Scaling a Python web application is rarely a linear path. Launching more EC2 instances behind a single IP creates a new problem: which instance should handle which request? Without a load balancer, you're stuck with a few bad options:
- Manual DNS switching — update
route53records every time you scale, which is slow and error-prone. - Round-robin DNS — gives each request a different IP but doesn't check if the target is healthy, so a failed instance still gets traffic.
- A third-party proxy — adds latency and cost, and you lose tight AWS integration.
None of these check health, none of them handle failures gracefully, and none of them scale with you. That's exactly what an Application Load Balancer does: it sits in front of your EC2 instances, listens on a stable DNS endpoint, and routes each request to a healthy target.
Core concept / mental model
Think of an ALB as a smart concierge in a hotel lobby. Guests (client requests) arrive at a single front desk (the ALB's DNS name). The concierge checks which rooms (backend instances) are clean and staffed (healthy) and then sends each guest to the most suitable room. If a room becomes unavailable, the concierge knows instantly and stops sending guests there.
In AWS terms, the ALB operates at Layer 7 of the OSI model, meaning it understands HTTP/S. It can route based on URL paths, hostnames, or headers — not just IP and port. Here are the key components:
- Load Balancer — the front-end service that receives all traffic and exposes a stable DNS name.
- Target Group — a logical grouping of EC2 instances (or other targets) that receive traffic.
- Listener — a process that checks for connection requests using a protocol and port (e.g., HTTP:80). The listener forwards traffic to the target group.
- Health Checks — periodic pings to each target's health check path (e.g.,
/health). If a target fails a configurable number of checks, it's marked unhealthy and removed from rotation.
When you set up an application load balancer with EC2, you're essentially creating this four-piece architecture. The ALB itself has a DNS name that never changes (even if you replace instances), so your clients only need to know one address.
How it works step by step
Here's the flow from request to response:
- Client sends a request to the ALB's DNS name (e.g.,
my-alb-1234567890.us-east-1.elb.amazonaws.com). - The listener accepts the connection on its configured port (say HTTP:80) and evaluates any rules you've defined (e.g., path-based routing).
- The target group selection — the ALB forwards the request to the target group associated with that listener.
- Health check filter — the ALB only routes to targets that have passed recent health checks.
- The target EC2 instance handles the request and returns a response.
- The ALB relays the response back to the client.
This happens in milliseconds, and the ALB can serve millions of concurrent requests across many instances. The key is that the client never talks directly to your EC2 instances — they only know the ALB's DNS name.
Hands-on walkthrough
Let's set up an application load balancer with EC2 using the AWS CLI. We'll assume you already have at least one EC2 instance running a simple Python web server (e.g., Flask) — from a prior lesson — and that you've configured the AWS CLI locally.
Step 1: Create a target group
Your target group defines how the ALB health-checks your instances and which port they listen on.
aws elbv2 create-target-group \
--name python-alb-tg \
--protocol HTTP \
--port 8000 \
--vpc-id vpc-0abc123def456 \
--health-check-protocol HTTP \
--health-check-path /health \
--healthy-threshold-count 3 \
--unhealthy-threshold-count 2
This creates a target group that expects your Flask app on port 8000 and pings /health every 30 seconds (default). Save the TargetGroupArn from the output.
Step 2: Register your EC2 instances
Attach your running EC2 instances to the target group so the ALB can route traffic to them.
aws elbv2 register-targets \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/python-alb-tg/src-tg-123 \
--targets Id=i-0a1b2c3d4e5f6,Port=8000 Id=i-0abcd1234ef56789,Port=8000
Double-check your instances' security groups allow inbound traffic on port 8000 from the ALB's security group — not from the internet.
Step 3: Create the Application Load Balancer
Now create the ALB and its listener.
aws elbv2 create-load-balancer \
--name python-alb \
--subnets subnet-0a1b2c3d4e5f6a7b8 subnet-0b2c3d4e5f6a7b8c9 \
--security-groups sg-0a1b2c3d4e5f6 \
--scheme internet-facing \
--type application
Note: You must specify at least two subnets across different Availability Zones for high availability. After creation, the ALB gets a DNS name — grab it for testing.
aws elbv2 create-listener \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/python-alb/1234567890abcdef \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/python-alb-tg/src-tg-123
Step 4: Test the ALB
Wait a few minutes for the ALB to become active, then send requests to its DNS name:
curl -v http://python-alb-1234567890.us-east-1.elb.amazonaws.com/
You should see your Flask app's response. To verify load distribution, run a loop and check instance IDs from your app (if you coded it to reveal the host).
Here's a tiny Flask app that shows which instance served the request:
from flask import Flask
import socket, os
app = Flask(__name__)
@app.route('/')
def home():
return f"Hello from instance {socket.gethostname()} (IP: {os.environ.get('PRIVATE_IP', 'unknown')})"
@app.route('/health')
def health():
return "OK", 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Run this on each EC2 instance via your automation (e.g., user_data or Ansible), then hit the ALB multiple times:
for i in {1..5}; do curl -s http://python-alb-1234567890.us-east-1.elb.amazonaws.com/; done
Expected output (order varies):
Hello from instance ip-10-0-1-10 (IP: 10.0.1.10)
Hello from instance ip-10-0-2-12 (IP: 10.0.2.12)
Hello from instance ip-10-0-1-10 (IP: 10.0.1.10)
Hello from instance ip-10-0-2-12 (IP: 10.0.2.12)
Hello from instance ip-10-0-1-10 (IP: 10.0.1.10)
If you see responses from both instances, your ALB is distributing traffic correctly.
Compare options / when to choose what
When it comes to load balancing in AWS, you have several choices. Here's a quick comparison:
| Feature | Application Load Balancer (ALB) | Network Load Balancer (NLB) | Classic Load Balancer (CLB) |
|---|---|---|---|
| OSI Layer | Layer 7 (HTTP/S) | Layer 4 (TCP/UDP) | Layer 4/7 (legacy) |
| Path/host-based routing | Yes | No (IP/port only) | Limited |
| Healthy target filtering | Yes | Yes | Yes |
| Lambda target | Yes | No | No |
| Microservices & containers | Excellent | Good | Poor |
| Performance | Moderate | Extremely high | Moderate |
| Cost | Higher (per LCU) | Lower per connection | Cheapest |
| Use case | Python web apps, APIs | Extreme TCP/UDP throughput | Legacy systems only |
When to choose what:
- ALB — your default for HTTP/S traffic, especially if your Python app has multiple services or paths.
- NLB — if you need raw TCP/UDP, extreme performance, or static IPs for your load balancer.
- CLB — only if you're migrating a legacy stack; AWS recommends against new deployments.
For most Python web applications, ALB is the right choice because it supports path-based routing, host-based routing, and direct integration with AWS Lambda and container services.
Variations: alternative approaches
- Path-based routing with multiple target groups — one ALB can route
/api/to one target group and/to another, perfect for microservices. - Use Terraform — instead of clicking in the console or typing CLI commands, define your ALB setup in
.tffiles and useterraform apply— a natural fit for the DevOps track. - HTTPS with ACM — attach an SSL certificate to your listener for encrypted traffic and automatic renewal (covered later in this track).
Troubleshooting & edge cases
Even with careful setup, things go wrong. Here are the most common issues and fixes:
- 502 Bad Gateway — Your application crashed, or the security group blocks the health check. Verify the app is running, then test directly from the instance:
curl http://localhost:8000/health. Also ensure your security group's inbound rule allows traffic from the ALB security group. - 504 Gateway Timeout — Your app takes too long to respond (ALB default timeout is 60 seconds). Optimize your code, or increase the idle timeout in the listener.
- Health checks failing — Check the health check path. If your Flask app doesn't have
/health, return 404, and the ALB marks it unhealthy. Add the route. - Targets registered but not healthy — Verify the instance is in the running state, the port is correct, and the security group allows the ALB's traffic.
- ALB not available — If you only specified one subnet, the ALB may still create but with lower resilience. Best practice: use at least two subnets.
Pro tip: Use the
aws elbv2 describe-target-healthcommand to see the health status of each target. It will tell you whether a target is healthy, unhealthy, or draining, and why.
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/python-alb-tg/src-tg-123
What you learned & what's next
You learned how to set up an application load balancer with EC2: you created a target group, registered your EC2 instances, created an ALB and listener, tested traffic distribution, and understood when to choose ALB over other load balancers. You can now explain the core idea behind an ALB and complete a practical exercise, meeting both learning objectives.
Next in the track, you'll explore AWS Route 53 — how to point a friendly domain name like app.example.com to your ALB's DNS name, and how to add SSL with AWS Certificate Manager for a production-grade setup.
Keep building!
Practice recap
Now that you've set up an ALB, try creating a second target group for a different path (like /api), register a third EC2 instance, and add a rule to your listener to route based on the path. Test both paths and observe how the ALB handles them. This will solidify your understanding before moving on to Route 53.
Common mistakes
- Forgetting to add a
/healthroute to your app, causing health checks to fail and instances to be marked unhealthy. - Setting security group rules to allow traffic from anywhere (0.0.0.0/0) on the app port, instead of restricting to the ALB's security group — this gives strangers direct access to instances.
- Creating the ALB with only one subnet, which prevents cross-AZ failover and makes your load balancer a single point of failure.
- Registering targets with the wrong port (e.g., port 80 instead of 8000) without updating the target group, leading to connection failures.
Variations
- Use Terraform to define your ALB, target groups, and listeners in code for reproducible infrastructure.
- Implement path-based routing on the same ALB to send
/api/requests to one target group and/to another. - Add HTTPS by attaching an AWS Certificate Manager (ACM) certificate to your listener.
Real-world use cases
- Scaling a Flask web application across multiple EC2 instances for high availability during traffic spikes.
- Running microservices where a single ALB routes
/usersto one Python service and/ordersto another based on URL paths. - Distributing traffic across instances in different Availability Zones to withstand an AZ failure.
Key takeaways
- An ALB distributes traffic at Layer 7 and supports path/host-based routing, unlike other AWS load balancers.
- The core components are the load balancer, target group, listener, and health checks.
- Health checks ensure only healthy instances receive traffic, preventing downtime.
- You must use at least two subnets across different Availability Zones for a resilient ALB.
- Security groups should allow traffic to instances only from the ALB, not the internet.
- Use
aws elbv2 describe-target-healthto debug unhealthy targets.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.