Back to blog
AWSBest PracticesCloud SecurityNetworkingReliability

Load Balancer Cross-Zone Disabled: Why It Causes Uneven Traffic and How to Fix It

Cross-zone load balancing disabled on AWS load balancers causes uneven traffic and overloaded zones. Learn why it matters and how to fix it with CLI and Terraform.

TL;DR

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.

  1. 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.
  2. 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.
  3. Standardize on modules. If everyone provisions load balancers through a shared Terraform module that sets enable_cross_zone_load_balancing = true by default, individual teams cannot forget it.
  4. Continuous scanning with Lensix. Keep the lb_nocrosszone check 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.

Fix AWS Load Balancer Cross-Zone Disabled | Lensix | Lensix