This check flags load balancers that have cross-zone load balancing turned off, which can cause uneven traffic distribution and overloaded backends in some availability zones. Enable cross-zone balancing on the load balancer (or target group for ALB/NLB) to spread requests evenly across all healthy targets.
Cross-zone load balancing sounds like a low-level networking detail, but it has a direct impact on how evenly your traffic lands on backend instances. When it is disabled, traffic gets distributed unevenly, and that imbalance can quietly degrade performance, waste capacity, and turn a single-zone slowdown into a customer-facing problem. This check exists because the disabled state is easy to miss and rarely surfaces until you are debugging a strange latency spike.
What this check detects
The lb_nocrosszone check looks at your AWS load balancers and reports any where cross-zone load balancing is disabled.
To understand the flag, you need to know how AWS routes traffic. A load balancer has nodes in each availability zone (AZ) you enable. When a request arrives, it hits the node in one of those zones first.
- Cross-zone enabled: each load balancer node distributes traffic across all registered healthy targets, regardless of which AZ they sit in.
- Cross-zone disabled: each node only sends traffic to targets in its own AZ.
Note: Defaults differ by load balancer type. Cross-zone is on by default for Application Load Balancers and cannot be turned off at the ALB level (only per target group). It is off by default for Network Load Balancers and Gateway Load Balancers. Classic Load Balancers default to off when configured via the API or CLI.
Why it matters
The problem is uneven traffic. DNS resolution for a load balancer hands out the IP of one zone's node, and clients tend to cache that. If the targets are spread unevenly across zones, some instances get hammered while others sit idle.
A concrete example
Imagine a Network Load Balancer with two AZs:
- us-east-1a: 2 instances
- us-east-1b: 8 instances
With cross-zone disabled, DNS round-robin sends roughly 50% of traffic to each zone's node. That means the 2 instances in us-east-1a absorb the same total load as the 8 instances in us-east-1b. Each instance in 1a now handles four times the requests of each instance in 1b. The smaller zone gets overloaded, response times climb, and your autoscaling group might scale up the wrong zone or not react at all because aggregate CPU looks fine.
Real-world consequences when cross-zone is off:
- Hot spots: targets in zones with fewer instances take a disproportionate share of traffic.
- Wasted capacity: you pay for instances that barely get traffic while others struggle.
- Fragile failover: if a zone loses most of its capacity, the surviving targets in that zone can be crushed before traffic shifts elsewhere.
- Misleading metrics: aggregate dashboards look healthy while individual instances are saturated.
Warning: Enabling cross-zone load balancing on an NLB has a cost implication. AWS charges for inter-AZ data transfer on traffic that crosses zone boundaries. For high-throughput NLBs this can be meaningful, so weigh the cost against the resilience benefit before flipping it on everywhere.
How to fix it
The remediation depends on the load balancer type. Below are the common cases.
Network Load Balancer (NLB) and Gateway Load Balancer
For NLB and GWLB, cross-zone is set as an attribute on the load balancer itself.
# Find the ARN of your load balancer
aws elbv2 describe-load-balancers \
--query 'LoadBalancers[].{Name:LoadBalancerName,Arn:LoadBalancerArn,Type:Type}' \
--output table
# Enable cross-zone load balancing
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:loadbalancer/net/my-nlb/abc123 \
--attributes Key=load_balancing.cross_zone.enabled,Value=true
Application Load Balancer (ALB)
ALBs always have cross-zone enabled at the load balancer level. The setting you control lives on each target group, where the default is to inherit from the load balancer (enabled). If a target group explicitly disabled it, re-enable it:
# Reset to inherit from the load balancer (recommended)
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/my-tg/abc123 \
--attributes Key=load_balancing.cross_zone.enabled,Value=use_load_balancer_configuration
Classic Load Balancer (CLB)
aws elb modify-load-balancer-attributes \
--load-balancer-name my-classic-lb \
--load-balancer-attributes "CrossZoneLoadBalancing={Enabled=true}"
Danger: Changing load balancer attributes takes effect immediately and shifts live traffic patterns. On a busy NLB, enabling cross-zone can suddenly route requests to targets that were previously idle. Confirm those targets are warm and healthy first, and apply the change during a low-traffic window if you are unsure how backends will respond.
Verify the change
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:loadbalancer/net/my-nlb/abc123 \
--query "Attributes[?Key=='load_balancing.cross_zone.enabled']"
Fixing it in infrastructure as code
Console and CLI fixes drift. Bake the setting into your IaC so it stays correct.
Terraform
resource "aws_lb" "nlb" {
name = "my-nlb"
internal = false
load_balancer_type = "network"
subnets = var.subnet_ids
enable_cross_zone_load_balancing = true
}
# For an ALB target group, leave the default or set explicitly:
resource "aws_lb_target_group" "tg" {
name = "my-tg"
port = 443
protocol = "TCP"
vpc_id = var.vpc_id
target_type = "instance"
load_balancing_cross_zone_enabled = "use_load_balancer_configuration"
}
CloudFormation
{
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Name": "my-nlb",
"Type": "network",
"Subnets": ["subnet-aaa", "subnet-bbb"],
"LoadBalancerAttributes": [
{
"Key": "load_balancing.cross_zone.enabled",
"Value": "true"
}
]
}
}
Tip: If you manage dozens of NLBs, write a small script that loops over describe-load-balancers, filters for Type == network, and patches any with the attribute set to false. Pair it with a dry-run flag so you can review the list before applying.
How to prevent it from happening again
Manual fixes solve today's problem. Guardrails stop it from coming back.
- Policy as code in CI/CD. Add a Checkov or OPA/Conftest rule that fails the pipeline when an NLB or GWLB is defined without cross-zone enabled. Checkov already ships with a relevant check for ELB attributes, and you can write a custom rule for the exact attribute you care about.
- AWS Config rule. Use a custom AWS Config rule to continuously evaluate load balancers and mark any with cross-zone disabled as non-compliant. This catches resources created outside your IaC pipeline.
- Standardize on modules. If everyone provisions load balancers through a shared Terraform module that sets
enable_cross_zone_load_balancing = trueby default, individual teams cannot forget it. - Continuous scanning with Lensix. Keep the
lb_nocrosszonecheck running so any drift, whether from a console change or a one-off CLI command, gets flagged automatically rather than discovered during an incident.
A simple Conftest policy snippet for Terraform plan JSON:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_lb"
resource.change.after.load_balancer_type == "network"
resource.change.after.enable_cross_zone_load_balancing == false
msg := sprintf("NLB '%s' has cross-zone load balancing disabled", [resource.address])
}
Best practices
- Balance targets across zones too. Cross-zone load balancing masks uneven target counts, but the cleanest setup keeps roughly equal capacity in each AZ. Configure autoscaling groups to balance across zones.
- Match the setting to your cost and resilience goals. For internal, high-throughput NLBs where inter-AZ transfer cost dominates and you keep zones balanced, leaving cross-zone off can be a deliberate choice. Document that decision so it is not mistaken for a misconfiguration.
- Use ALB defaults. Unless you have a specific reason, leave target groups set to inherit the load balancer configuration rather than overriding cross-zone per target group.
- Watch per-target metrics. Set CloudWatch alarms on per-target request counts and response times, not just aggregates, so a hot zone surfaces quickly.
- Test failover. Periodically simulate losing a zone's targets to confirm traffic shifts the way you expect and surviving capacity can absorb it.
Cross-zone load balancing is one of those settings that costs almost nothing to get right and quietly causes pain when it is wrong. Decide on a default, enforce it in code, and let continuous checks catch the exceptions before your users do.

