Target Groups
A load balancer needs to know where to send incoming traffic. In AWS Elastic Load Balancing (ELB — the managed service that spreads traffic across many servers), that “where” is a target group. A target group is a named bucket of backend destinations (called targets) plus the rules for checking whether each one is healthy. Your load balancer’s listener forwards requests to a target group, and the target group decides which healthy target actually serves the request. Getting target groups right is the difference between a smooth deployment and one where traffic silently disappears.
What a target group actually is
Think of a target group as three things bundled together:
- A list of registered targets — the actual backends that will receive traffic.
- A protocol and port — how the load balancer talks to those targets (for example HTTP on port 80).
- A health check — a probe the load balancer runs against each target to decide if it should receive traffic.
One load balancer can route to many target groups (for example /api/* to one group and everything else to another), and one target group can be shared by multiple load balancers.
Target types — instance vs IP vs Lambda
When you create a target group you must pick a target type, and you cannot change it later. This choice controls what kind of backend you can register.
| Target type | What you register | Best for | Notes |
|---|---|---|---|
instance | EC2 instance IDs (e.g. i-0a1b2c3d4e5f) | Classic EC2 fleets, Auto Scaling Groups | Load balancer uses the instance’s primary private IP |
ip | Private IPv4/IPv6 addresses | Containers (ECS/EKS), on-prem servers reached over VPN/Direct Connect, microservices | Lets you target backends outside the VPC’s normal EC2 flow |
lambda | A single Lambda function | Serverless HTTP backends behind an Application Load Balancer | ALB only; NLB cannot invoke Lambda |
When to use which: Use
instancefor ordinary EC2 servers, especially with Auto Scaling. Useipwhen you run containers or need to reach IPs that aren’t plain EC2 instances. Uselambdaonly with an Application Load Balancer (ALB — the Layer-7 load balancer that understands HTTP) when you want a serverless backend.
Creating a target group
Console steps
- Open the EC2 console, and in the left menu under Load Balancing choose Target Groups.
- Click Create target group.
- Choose a target type (
Instances,IP addresses, orLambda function). - Enter a Target group name, for example
web-tg. - Set the Protocol and Port the load balancer uses to reach targets, for example
HTTPand80. - Pick the VPC (your virtual network), for example
vpc-0a1b2c3d. - Expand Health checks and set the protocol and path (for example
/health). - Click Next, register your targets, then Create target group.
CLI equivalent
aws elbv2 create-target-group \
--name web-tg \
--protocol HTTP \
--port 80 \
--vpc-id vpc-0a1b2c3d \
--target-type instance \
--health-check-protocol HTTP \
--health-check-path /health \
--healthy-threshold-count 3 \
--unhealthy-threshold-count 2
Output:
{
"TargetGroups": [
{
"TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067",
"TargetGroupName": "web-tg",
"Protocol": "HTTP",
"Port": 80,
"VpcId": "vpc-0a1b2c3d",
"TargetType": "instance",
"HealthCheckPath": "/health"
}
]
}
Registering and deregistering targets
Creating the group is empty until you put targets in it. Registering a target tells the load balancer to start health-checking it and (once healthy) sending it traffic.
Console steps
- Open Target Groups and click your group (
web-tg). - Go to the Targets tab and click Register targets.
- Select the instances (or enter IPs), set the port, and click Include as pending below.
- Click Register pending targets.
CLI equivalent
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
To check status:
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.FailedHealthChecks",
"Description": "Health checks failed"
}
}
]
}
To remove a target (for example before terminating an instance), deregister it:
aws elbv2 deregister-targets \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067 \
--targets Id=i-0f9e8d7c6b5a
The big gotcha: “unhealthy” usually isn’t the app’s fault
When a target shows unhealthy, the instinct is to blame the application. Most of the time the app is fine — the health check can’t reach it. Always check the health-check configuration first. The two most common causes:
- A security group blocks the health-check port. The load balancer probes each target on the health-check port. If the target’s security group (a virtual firewall) doesn’t allow inbound traffic from the load balancer’s security group on that port, every check fails and the target is marked unhealthy even though it’s serving traffic perfectly to everyone else.
- The health-check path returns a non-200 status. A target is only healthy if the path returns a success code (by default
200). If/healthreturns a301redirect, a403, or a404because the route doesn’t exist, the check fails.
Warning: Before debugging your code, run
describe-target-healthand read theReason.Elb.RegistrationInProgressjust means “wait.”Target.Timeoutalmost always means a security group or network ACL is blocking the probe.Target.ResponseCodeMismatchmeans the path returned the wrong HTTP status — fix the path or the expectedMatcher.
A quick fix for the security-group case — allow the load balancer’s security group to reach the targets:
aws ec2 authorize-security-group-ingress \
--group-id sg-0a1b2c3d \
--protocol tcp \
--port 80 \
--source-group sg-0e1f2a3b
You can also widen the accepted status codes if your health endpoint legitimately returns something other than 200:
aws elbv2 modify-target-group \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/web-tg/73e2d6bc24d8a067 \
--matcher HttpCode=200,301
Defining a target group as code
Target groups are easy to drift between environments, so defining them in infrastructure-as-code keeps them consistent. Here it is in CloudFormation:
WebTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: web-tg
VpcId: vpc-0a1b2c3d
Protocol: HTTP
Port: 80
TargetType: instance
HealthCheckPath: /health
HealthCheckIntervalSeconds: 15
HealthyThresholdCount: 3
UnhealthyThresholdCount: 2
Matcher:
HttpCode: "200"
Cost note
Target groups themselves are free — you only pay for the load balancer and its capacity units (LCUs). A standalone Application Load Balancer is about $0.0225 per hour (roughly $16/month) plus LCU charges, regardless of how many target groups you attach to it. So splitting traffic into several target groups costs nothing extra.
Best Practices
- Use a dedicated health-check path like
/healththat checks dependencies (database, cache) rather than reusing/, which may return200even when the app is broken. - Always allow the load balancer’s security group inbound on the health-check port — reference it as a source security group, not a raw IP range.
- Keep the target type correct from the start (
instance,ip, orlambda) since it cannot be changed after creation. - Tune
HealthyThresholdCount,UnhealthyThresholdCount, and the interval so transient blips don’t yank a healthy target out of rotation. - Set a sensible deregistration delay (connection draining) so in-flight requests finish before a target is removed during deploys or scale-in.
- Define target groups in CloudFormation or Terraform so dev, staging, and production stay identical.