1. Find idle and near-idle load balancers
for arn in $(aws elbv2 describe-load-balancers --query 'LoadBalancers[?Type==`application`].LoadBalancerArn' --output text); do
name=$(echo "$arn" | cut -d/ -f2-4)
sum=$(aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount \
--dimensions Name=LoadBalancer,Value="$name" --start-time "$(date -u -d '-14 days' +%FT%TZ)" \
--end-time "$(date -u +%FT%TZ)" --period 1209600 --statistics Sum --query 'Datapoints[0].Sum' --output text)
echo -e "$sum\t$name"
done | sort -n | head -20
For NLBs use AWS/NetworkELB NewFlowCount or ProcessedBytes. Also look for load balancers with no healthy targets or no registered targets at all. Trusted Advisor flags idle load balancers too.
2. Consolidate small apps
One ALB supports many listener rules (host and path based), so many low-traffic apps can share one:
alb-internal-shared
├─ host: hr.internal.example → tg-hr
├─ host: wiki.internal.example → tg-wiki
└─ host: reports.internal.example → tg-reports
Guidelines:
- Group by environment and exposure (internal vs internet-facing), never prod with dev.
- Use SNI with multiple ACM certificates on the HTTPS listener.
- Watch the rules-per-listener and certificates-per-listener quotas.
- On EKS, the AWS Load Balancer Controller's IngressGroup (
alb.ingress.kubernetes.io/group.name) merges many Ingresses onto one ALB.
3. ALB vs NLB
| Need | Choose |
|---|---|
| HTTP routing, WAF, auth (OIDC/Cognito), redirects | ALB |
| TCP/UDP, static IPs, extreme throughput, preserve source IP | NLB |
| PrivateLink service provider endpoint | NLB |
NLB cross-zone load balancing is off by default and cross-AZ traffic is charged when on. Decide based on target distribution across AZs.
4. Retire Classic Load Balancers
Classic LBs lack modern features and cost similar or more. Migrate with the console's migration wizard or by creating an ALB/NLB, testing, then switching DNS.
5. Target group settings that matter
| Setting | Default | Tune to |
|---|---|---|
| Deregistration delay | 300 s | Slightly above your longest normal request (often 30–60 s). Faster deployments and scale-in |
| Health check interval / thresholds | 30 s / 5 healthy, 2 unhealthy | Faster detection for critical apps (e.g. 10 s, 3/2) |
| Health check path | / | Dedicated lightweight endpoint that checks dependencies sensibly |
| Slow start | Off | 30–120 s for apps with cold caches/JIT warm-up |
| Load balancing algorithm | Round robin | Least outstanding requests for uneven request durations |
| Stickiness | Off | Only if the app truly needs it; it hurts balancing |
aws elbv2 modify-target-group-attributes --target-group-arn <tg-arn> --attributes \
Key=deregistration_delay.timeout_seconds,Value=45 Key=slow_start.duration_seconds,Value=60 \
Key=load_balancing.algorithm.type,Value=least_outstanding_requests
6. Idle timeouts and keep-alive
ALB idle timeout (default 60 s) should be lower than the backend's keep-alive timeout. Otherwise the backend closes connections the ALB still uses, which shows up as random 502s.
7. Clean up
Delete idle load balancers after confirming DNS records (Route 53, external DNS) no longer point to them, and remove their security groups and target groups.
Warning
Before deleting a "zero-request" load balancer, check whether it's a DR standby or used only for a monthly/quarterly process. Look at 90 days of metrics and ask the owner.
Checklist
- Idle and target-less load balancers listed
- Small apps consolidated per environment/exposure
- ALB vs NLB chosen per need; NLB cross-zone decided
- Classic LBs migrated
- Target group and idle timeout settings tuned
- Unused LBs removed after DNS and owner checks