Skip to main content
INS // Insights

AWS Idle Load Balancer Cost Cleanup Guide

Updated August 2026 · 7 min read

An Application or Network Load Balancer bills an hourly rate plus a Load Balancer Capacity Unit (LCU) charge regardless of whether it's routing any real traffic. A load balancer created for a service that was later decommissioned, a staging environment stood up for a project that shipped and moved on, or a blue-green deployment target that never got cleaned up after the cutover — all keep billing the hourly rate indefinitely with zero business value, and none of them show up as an obvious anomaly on a cost dashboard because the charge is small and steady, not a spike.

Finding Load Balancers With No Real Traffic

# List all ALBs/NLBs and their target group health — zero healthy targets is the first signal
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].[LoadBalancerArn,LoadBalancerName]' --output text | \
while read -r arn name; do
  target_groups=$(aws elbv2 describe-target-groups --load-balancer-arn "$arn" --query 'TargetGroups[*].TargetGroupArn' --output text)
  healthy_count=0
  for tg in $target_groups; do
    healthy=$(aws elbv2 describe-target-health --target-group-arn "$tg" \
      --query "length(TargetHealthDescriptions[?TargetHealth.State=='healthy'])")
    healthy_count=$((healthy_count + healthy))
  done
  if [ "$healthy_count" -eq 0 ]; then
    echo "$name ($arn): 0 healthy targets"
  fi
done

Zero healthy targets is a strong first signal but not conclusive on its own — a load balancer could have zero targets registered and still be an active edge for a service using target group weighting during a deployment. The confirming check is CloudWatch's RequestCount (for ALB) or ActiveFlowCount (for NLB) metric over a meaningful window — 30 days with zero requests routed is a much stronger signal that the resource is safe to decommission.

# Confirm zero real traffic over 30 days before decommissioning
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name RequestCount \
  --dimensions Name=LoadBalancer,Value=app/my-alb/50dc6c495c0c9188 \
  --start-time "$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%S)" \
  --period 2592000 \
  --statistics Sum

Orphaned Listeners and Unused Target Groups Cost Money Too

Target groups don't bill directly, but an orphaned target group left attached to nothing, or a listener rule referencing a decommissioned service, is the kind of drift that makes an AWS account harder to reason about and audit — every unused resource is one more thing a security review or cost review has to manually rule out as "actually fine" rather than obviously clean. Cleaning these up isn't a direct cost saving on its own, but it's part of the same hygiene pass that catches actual idle load balancers, and a cluttered account is where real cost leaks hide longest.

Automating the Prevention, Not Just the Cleanup

A one-time cleanup finds today's waste. The recurring version — a scheduled Lambda that flags (not auto-deletes, given the blast radius of deleting the wrong load balancer) any load balancer with zero traffic over a rolling 30-day window — prevents the same waste from re-accumulating after the next round of service decommissions.

# idle_alb_finder.py — scheduled weekly, posts findings to Slack for manual review
import boto3
from datetime import datetime, timedelta

elbv2 = boto3.client("elbv2")
cloudwatch = boto3.client("cloudwatch")

def find_idle_load_balancers(days_threshold: int = 30) -> list[str]:
    idle = []
    load_balancers = elbv2.describe_load_balancers()["LoadBalancers"]

    for lb in load_balancers:
        stats = cloudwatch.get_metric_statistics(
            Namespace="AWS/ApplicationELB",
            MetricName="RequestCount",
            Dimensions=[{"Name": "LoadBalancer", "Value": lb["LoadBalancerArn"].split("loadbalancer/")[-1]}],
            StartTime=datetime.utcnow() - timedelta(days=days_threshold),
            EndTime=datetime.utcnow(),
            Period=days_threshold * 86400,
            Statistics=["Sum"],
        )
        total_requests = sum(dp["Sum"] for dp in stats["Datapoints"])
        if total_requests == 0:
            idle.append(lb["LoadBalancerName"])
    return idle

Flag-and-review beats auto-delete for this specific resource type — the cost of a false positive (deleting a load balancer that turns out to matter) is much higher than the cost of the idle resource itself running one more week while a human confirms it's safe to remove.

Frequently Asked Questions

How much does an idle load balancer actually cost per month?

An Application Load Balancer's hourly charge alone runs roughly $16-20/month depending on region, before any LCU usage charges. That sounds small per resource, but accounts that have accumulated a dozen or more forgotten load balancers over a year of project churn are looking at hundreds of dollars a month in pure waste with zero traffic to show for it.

Is it safe to assume zero RequestCount means the load balancer is unused?

For ALB, yes with high confidence over a 30-day window — RequestCount captures all HTTP/HTTPS requests routed through it. For NLB, use ActiveFlowCount and NewFlowCount instead, since NLB operates at the connection level rather than the request level and RequestCount isn't the right metric there.

Why do idle load balancers accumulate in the first place?

Almost always from incomplete decommissioning — a team ships a new architecture, migrates traffic to a new load balancer, and never circles back to delete the old one because there's no ownership assigned to infrastructure teardown the way there is to infrastructure creation. Blue-green deployment patterns are a particularly common source if the "blue" side isn't explicitly torn down after cutover.

Should load balancer cleanup be part of a regular cost review cadence?

Yes — this class of waste (idle load balancers, unattached EBS volumes, unused Elastic IPs, forgotten NAT gateways) tends to reaccumulate steadily as projects ship and get decommissioned, so a monthly or quarterly automated scan catches it early rather than letting a year of drift compound into a much larger cleanup project.

Can Terraform prevent this kind of drift going forward?

Partially — if load balancers are only ever created and destroyed through Terraform with a disciplined terraform destroy step as part of decommissioning a service, drift is much less likely. The gap is usually process, not tooling: teams that create infrastructure through IaC but decommission manually (or forget to decommission at all) still accumulate exactly this kind of waste.


Rutagon's AWS cost audits include idle resource detection — load balancers, Elastic IPs, EBS volumes, and NAT gateways — with a scheduled scan you keep after the engagement ends.

Talk to us about a cost optimization audit → rutagon.com/contact | 907-841-8407 | contact@rutagon.com

Related reading: AWS Idle Elastic IP Cost Cleanup · AWS NAT Gateway Cost Reduction Guide · AWS Cloud Infrastructure Capability

External reference: AWS Elastic Load Balancing Pricing