Idle load balancers: the basics
What does AWS mean by an "idle" load balancer?
An Elastic Load Balancer (Classic, Application (ALB), Network (NLB), or Gateway) is a managed AWS resource that bills by the hour the moment you create it, whether or not anything is using it. ALB and NLB each cost around $0.0225 per hour (roughly $16.20 per month, per LB, per region) just for existing. On top of that you pay LCUs (Load Balancer Capacity Units) for actual traffic, which trends to zero on an idle one, but the hourly floor never moves.
An "idle" LB is one that has no healthy targets and no real traffic: target groups are empty or unhealthy, ProcessedBytes and RequestCount are sitting near zero, and ActiveConnectionCount has been flat at zero for a week or more. It's still listening, still billing, still showing up in your inventory. Nothing is hitting it because there's nothing behind it.
AWS Trusted Advisor and Cost Optimization Hub flag these as a wastage finding because the fix is unambiguous: an LB with no targets and no traffic for 7+ days is overwhelmingly forgotten infrastructure, not a deliberate hot standby. The per-LB savings look small ($16/mo) but multiply by a few dozen across regions and accounts and you're looking at real money for no work.
In this lesson you'll learn how to detect idle load balancers, the three patterns that usually create them, how to investigate whether anything still references one before you delete it, and the correct cleanup sequence so you don't leave orphaned target groups and listeners behind. You'll see real AWS CLI calls against ELBv2 and the CloudWatch metrics that confirm the verdict.
The Classic ELB that wouldn't die
Classic Load Balancers (elb, not elbv2) are the original 2009-era ELB type that AWS has been quietly trying to retire for a decade. Most large accounts have a handful still running, usually attached to something nobody on the current team built, often pointed at instances that were terminated years ago. They keep billing at $0.025/hour because AWS will never auto-delete a resource you might have a reason to keep. One fintech audit found 47 Classic ELBs across 12 accounts, none with healthy targets, all created before 2018; at $0.025/hour that's about $10k/year being paid to listen for connections that would never arrive.
Idle LB cleanup in action
Nina is doing a quarterly waste sweep on the platform team's AWS estate. The wastage report surfaces 8 ALBs and 2 NLBs flagged as idle across three regions: combined annual run-rate of about $1,950 just in hourly charges.
She picks the worst offender: an ALB named legacy-api-prod in eu-west-1. The wastage detail says it has zero healthy targets and ActiveConnectionCount has been flat at 0 for 23 days. Before she touches it, she needs to be certain nothing (Route53, CloudFront, a partner integration) is still pointed at its DNS name.
Step one is confirming the target group state. Step two is checking the CloudWatch metrics herself. Step three is grep'ing the rest of the account for any reference to the LB's DNS name or ARN. Only then does she pull the trigger on the delete.
First, look at the LB's target groups and their health to confirm there's nothing behind it.
Zero registered targets and the LB still in active state: paying the hourly fee for no reason.
Now confirm with CloudWatch that no real traffic has hit it in weeks before pulling the trigger.
23 days of CloudWatch confirms there's no traffic: this is genuinely dead.
Idle load balancers under the hooddeep dive
ELB billing has two components: a flat hourly rate per LB and an LCU rate per processed unit of traffic (new connections, active connections, processed bytes, and rule evaluations for ALBs). On an idle LB the LCU side rounds to a few cents, but the hourly rate is unconditional. AWS bills $0.0225/hour for ALB and NLB and $0.025/hour for Classic ELB regardless of traffic, which is where the ~$16-18/month floor comes from. You can't pause an LB; you can only delete it.
An LB ends up idle in one of three classic patterns. First, the target group emptied out: an Auto Scaling Group scaled to zero overnight, the instances behind it were terminated, or an ECS service was deleted but the LB it fronted wasn't. Second, a DNS cutover left the old LB orphaned: the team pointed Route53 at a new ALB and never deleted the old one, which keeps listening forever. Third, a blue/green deployment created a parallel LB and the rollback or cutover left the inactive one behind.
The detection signal is unambiguous: describe-target-health returns no entries (or all unused/unhealthy), ProcessedBytes and RequestCount round to zero, and ActiveConnectionCount has been zero for at least 7 days. Wastage scanners require the 7-day window because some LBs are legitimately quiet (DR standbys, low-volume internal tools); anything shorter generates too many false positives. Anything longer than 7 days at zero is genuinely dead in 95%+ of cases.
# List every ALB/NLB in a region, then check each for healthy targets.
for lb_arn in $(aws elbv2 describe-load-balancers --query 'LoadBalancers[*].LoadBalancerArn' --output text); do
tg_arns=$(aws elbv2 describe-target-groups --load-balancer-arn "$lb_arn" --query 'TargetGroups[*].TargetGroupArn' --output text)
healthy=0
for tg in $tg_arns; do
count=$(aws elbv2 describe-target-health --target-group-arn "$tg" \
--query 'length(TargetHealthDescriptions[?TargetHealth.State==`healthy`])' --output text)
healthy=$((healthy + count))
done
if [ "$healthy" -eq 0 ]; then
echo "IDLE: $lb_arn"
fi
done What is the impact of leaving idle load balancers running?
The direct cost is modest per resource (about $16-18 per month per LB) but it scales viciously across an estate. A mid-sized account commonly accumulates 20-50 idle LBs across regions and environments over a few years; that's $4k-$10k a year evaporating with literally zero workload to show for it. Multi-account organisations with dev/staging/prod replication see this in the tens of thousands per year, and it grows monotonically because nobody owns deletion.
The second-order cost is operational drag. Idle LBs clutter the AWS console, complicate Terraform/CloudFormation drift detection, and confuse incident response: engineers troubleshooting outages have to triage "is this LB actually serving anything?" for every result in a search. CloudWatch dashboards aggregate metrics across LBs by name pattern; idle ones inflate counts without contributing signal.
There's a security angle too. An LB that nobody owns is one nobody is patching the listener config on. TLS policies drift to deprecated versions, security groups stay open to 0.0.0.0/0 from the day they were created, and access logs (if enabled at all) pile up in an S3 bucket with no retention policy. The day someone actually plugs an instance back into that orphaned target group, the security posture is whatever it was three years ago.
Finally: tagged ownership matters here. An LB without an owner or team tag is itself a flag: the absence of a tag tells you no one has put their name on it, and that's almost always a sign it's been forgotten. Cleaning up idle LBs is partly a forcing function for getting tag hygiene right on the survivors.
How do you clean up and prevent idle load balancers?
The cleanup is a four-step loop: inventory the idle ones, confirm nothing still references them, delete the LB and its dependent listeners and target groups, then put in place the lifecycle controls that prevent the next round of accumulation.
1. Inventory all LBs and filter by health and traffic
Run describe-load-balancers across every region; LBs are regional resources and idle ones love to hide in regions nobody checks. For each LB, pull its target groups, check target health, and then cross-reference with the CloudWatch ActiveConnectionCount and ProcessedBytes metrics over a 7+ day window. Anything with zero healthy targets and zero traffic for that window is a candidate. Include Classic ELBs (aws elb describe-load-balancers) separately; they live under a different API and are often the worst offenders.
2. Confirm nothing still references the DNS name or ARN
Before deleting, grep your account for the LB's DNS name and ARN. Check Route53 alias records (aws route53 list-resource-record-sets), CloudFront distributions for origins pointing at the LB, ECS service definitions, EKS Ingress annotations, and any Terraform or CloudFormation that might reference it. A 7-day idle window doesn't rule out a quarterly batch job or a partner who hasn't migrated yet. If you find a reference, fix the reference first; if you find none, you're safe to delete.
3. Delete in the right order: listeners, LB, then target groups
Deleting an LB does NOT delete its target groups; they stay behind, costing nothing on their own but cluttering inventory and accumulating into another wastage finding. The sequence is: describe-listeners to see what's attached, delete-load-balancer (which removes the LB and its listeners in one call), then delete-target-group for each orphaned target group. For Classic ELBs the API is aws elb delete-load-balancer (different namespace, same idea). Keep the LB ARN, target group ARNs, and listener configs in a rollback note for at least 30 days in case you misjudged.
4. Prevent recurrence with lifecycle tags and managed creation paths
ALBs created by Kubernetes Ingress via the AWS Load Balancer Controller get garbage-collected automatically when the Ingress object is deleted; that's the right pattern. For LBs created manually or via Terraform, enforce a tagging policy that requires owner, team, and lifecycle (e.g. permanent or temp-until=2026-09-01) at creation, blocked by an SCP if missing. Run the inventory scan as a monthly cron and surface findings in the FinOps review.
# Cleanup sequence for a single idle LB. Capture state first for rollback.
LB_ARN="arn:aws:elasticloadbalancing:eu-west-1:123456789012:loadbalancer/app/legacy-api-prod/abc123"
# 1. Snapshot the LB and target group config to a JSON file.
aws elbv2 describe-load-balancers --load-balancer-arns "$LB_ARN" > backup-lb.json
aws elbv2 describe-listeners --load-balancer-arn "$LB_ARN" > backup-listeners.json
TG_ARNS=$(aws elbv2 describe-target-groups --load-balancer-arn "$LB_ARN" \
--query 'TargetGroups[*].TargetGroupArn' --output text)
# 2. Delete the LB (listeners go with it).
aws elbv2 delete-load-balancer --load-balancer-arn "$LB_ARN"
# 3. Delete the now-orphaned target groups.
for tg in $TG_ARNS; do
aws elbv2 delete-target-group --target-group-arn "$tg"
done Quick quiz
Question 1 of 5An ALB has zero healthy targets, ActiveConnectionCount has been 0 for 14 days, and nothing in Route53 or CloudFront references its DNS name. What's the safe cleanup sequence?
You scored
0 / 5
Keep learning
Dig deeper into ELB pricing, lifecycle, and the patterns that keep load balancers from going stale.
- Elastic Load Balancing: pricing details Hourly and LCU rates for ALB, NLB, GWLB, and Classic ELB across regions.
- AWS Load Balancer Controller for Kubernetes The managed-creation path that auto-deletes ALBs when their Ingress objects go away.
- AWS Trusted Advisor: idle load balancers check AWS's official Idle Load Balancers cost-optimization check (the AWS-side finding behind what we track internally as NET-003).
- FinOps Foundation: workload management & automation Where idle-resource cleanup sits in the broader FinOps operating model.
You've completed Delete idle load balancers. You now know how to spot an LB billing for no reason, confirm nothing still depends on it, and clean up the LB and its target groups in the right order, plus the lifecycle tags and managed-creation patterns that stop the next batch from piling up. The next time the wastage report flags a NET-003, you'll have the inventory-confirm-delete-prevent loop ready to run.