Creating an Application Load Balancer (Step by Step)
An Application Load Balancer (ALB) is a managed AWS service that spreads incoming web traffic across several backend servers so no single server gets overwhelmed. It works at Layer 7 (the application layer, meaning it understands HTTP and HTTPS), so it can route requests based on the URL path or hostname. In this page you will create one end to end: the load balancer itself, a target group (the pool of servers that receive traffic), a listener (the rule that says “accept traffic on this port”), and a health check (an automatic test that decides which servers are healthy enough to receive traffic). Getting these four pieces right — especially the security groups — is what separates a working setup from the dreaded “all targets unhealthy” error.
What you need before you start
Before creating an ALB, make sure you have these in place. A VPC (Virtual Private Cloud — your own private network inside AWS) with at least two subnets in two different Availability Zones (AZs, which are physically separate data centers in a Region). You also need one or more targets to send traffic to: an EC2 instance (a virtual server), an IP address, or a Lambda function.
Gotcha: An ALB requires subnets in at least two AZs. If you only select one subnet, the console will not let you continue. This is by design — it forces your application to survive the loss of a single data center.
Step 1 — Create a target group
A target group is the list of backend resources the ALB forwards requests to. You create it first because the listener (built later) needs to point at it.
Console steps:
- Open the EC2 console and choose Target Groups under “Load Balancing” in the left menu.
- Click Create target group.
- Choose a target type. Pick Instances for EC2 servers, IP addresses for containers or on-prem hosts, or Lambda function for serverless.
- Name it
web-tg, set protocol HTTP and port 80, and select your VPC. - Under Health checks, set the path to
/health(or/if you have no dedicated health endpoint) and leave the protocol as HTTP. - Click Next, select the EC2 instances to register, click Include as pending below, then Create target group.
AWS CLI equivalent:
aws elbv2 create-target-group \
--name web-tg \
--protocol HTTP \
--port 80 \
--vpc-id vpc-0a1b2c3d \
--target-type instance \
--health-check-path /health \
--health-check-interval-seconds 30 \
--healthy-threshold-count 3
Output:
{
"TargetGroups": [
{
"TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067",
"TargetGroupName": "web-tg",
"Protocol": "HTTP",
"Port": 80,
"VpcId": "vpc-0a1b2c3d",
"HealthCheckPath": "/health"
}
]
}
Step 2 — Create a security group for the ALB
A security group is a virtual firewall. The ALB needs one that allows inbound traffic on the listener port (80 for HTTP, 443 for HTTPS).
aws ec2 create-security-group \
--group-name alb-sg \
--description "Allow HTTP/HTTPS to ALB" \
--vpc-id vpc-0a1b2c3d
# Then allow port 80 from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d \
--protocol tcp --port 80 --cidr 0.0.0.0/0
The #1 cause of unhealthy targets: mismatched security groups. The ALB’s security group must allow the listener port inbound, and your targets’ security group must allow traffic from the ALB’s security group on the target port. Reference the ALB’s group as the source, not a CIDR block:
aws ec2 authorize-security-group-ingress \ --group-id sg-0target123 \ --protocol tcp --port 80 \ --source-group sg-0a1b2c3d
Step 3 — Create the load balancer
Now create the ALB itself, choosing its scheme and subnets.
Scheme — when to use which:
| Scheme | Public IP? | Use when |
|---|---|---|
internet-facing | Yes | Your app serves users on the public internet (a website, public API). |
internal | No (private IPs only) | Traffic comes from inside your VPC — internal microservices, backend tiers. |
Console steps:
- In the EC2 console, choose Load Balancers, then Create load balancer.
- Under “Application Load Balancer”, click Create.
- Name it
web-alband choose the scheme (Internet-facing for a public site). - Under Network mapping, select your VPC and tick at least two AZs, choosing one subnet in each.
- Under Security groups, select
alb-sg(remove the default group if present). - Skip the listener for now — you’ll refine it next. Click Create load balancer.
AWS CLI equivalent:
aws elbv2 create-load-balancer \
--name web-alb \
--type application \
--scheme internet-facing \
--subnets subnet-0a1b2c3d subnet-0e4f5a6b \
--security-groups sg-0a1b2c3d
Output:
{
"LoadBalancers": [
{
"LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:111122223333:loadbalancer/app/web-alb/50dc6c495c0c9188",
"DNSName": "web-alb-1234567890.us-east-1.elb.amazonaws.com",
"Scheme": "internet-facing",
"Type": "application",
"State": { "Code": "provisioning" }
}
]
}
The DNSName is the public address you give your users (or point a DNS record at). An ALB does not get a static IP — you always use this name.
Step 4 — Create the listener
A listener checks for connection requests on a port and forwards them to a target group. Here you connect port 80 to web-tg.
aws elbv2 create-listener \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:loadbalancer/app/web-alb/50dc6c495c0c9188 \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067
Step 5 — Register targets and verify health
If you did not register instances when creating the target group, do it now.
aws elbv2 register-targets \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067 \
--targets Id=i-0a1b2c3d4e5f Id=i-0f9e8d7c6b5a
Then check that the targets passed their health checks:
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067
Output:
{
"TargetHealthDescriptions": [
{
"Target": { "Id": "i-0a1b2c3d4e5f", "Port": 80 },
"TargetHealth": { "State": "healthy" }
},
{
"Target": { "Id": "i-0f9e8d7c6b5a", "Port": 80 },
"TargetHealth": { "State": "unhealthy", "Reason": "Target.Timeout" }
}
]
}
A Target.Timeout reason almost always means the targets’ security group is blocking the ALB. Go back to Step 2 and confirm the rule.
The whole thing in CloudFormation
For repeatable, version-controlled infrastructure, define it as code instead of clicking. This is the recommended approach for anything beyond a quick experiment.
Resources:
WebTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: web-tg
Port: 80
Protocol: HTTP
VpcId: vpc-0a1b2c3d
HealthCheckPath: /health
WebALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: web-alb
Scheme: internet-facing
Type: application
Subnets: [subnet-0a1b2c3d, subnet-0e4f5a6b]
SecurityGroups: [sg-0a1b2c3d]
WebListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref WebALB
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref WebTargetGroup
Cost note: An ALB costs about $0.0225 per hour (roughly $16/month) plus a usage charge measured in LCUs (Load Balancer Capacity Units, which bundle connections, bandwidth, and rule evaluations). A low-traffic app typically runs $18-25/month. There is no charge for an ALB you delete, so tear down test load balancers when you’re done.
Best practices
- Always spread targets across two or more AZs so the loss of one data center does not take your site down.
- Reference the ALB’s security group as the source in your target SG rules — never open targets to
0.0.0.0/0directly. - Use a dedicated
/healthendpoint that checks real dependencies (database, cache) rather than a path that always returns 200. - Add an HTTPS listener on port 443 with an ACM (AWS Certificate Manager) certificate for production, and redirect port 80 to 443.
- Enable access logs to S3 so you can debug traffic and unhealthy-target issues after the fact.
- Define your ALB in CloudFormation or Terraform so the setup is reviewable, repeatable, and easy to delete.