← All posts

Cross-AZ data transfer: the silent tollbooth in multi-AZ architectures

Published August 4, 2025

AWS Well-Architected guidelines recommend deploying workloads across multiple Availability Zones (AZs) for resilience. If an entire data center facility loses power or network connectivity, your application continues running in the surviving zones.

What the reference architectures often skip is the networking cost: moving data across AZ boundaries inside the same region is not free. AWS meters inter-AZ traffic at $0.01 per GB sent and $0.01 per GB received, creating a $0.02 per GB total round-trip tax on internal communication.

For monolithic applications with light database traffic, this fee is negligible. But in modern distributed environments—Kubernetes clusters running hundreds of microservices, multi-node Kafka pipelines, Elasticsearch clusters, and caching layers—cross-AZ data transfer often becomes one of the largest single lines on the AWS bill, generating thousands of dollars in monthly costs without serving a single byte of external internet egress.

Here is how cross-AZ billing works, where the hidden terabytes accumulate, and four practical engineering fixes to cut internal data transfer costs.

The mechanics of cross-AZ data transfer pricing

According to AWS networking data in our networking section, data transfer within a VPC falls into three pricing categories:

Traffic Scope Sent Rate Received Rate Total Rate (Round-Trip Flow)
Same Subnet / Same AZ (Private IP) $0.00/GB $0.00/GB $0.00/GB (Free)
Inter-AZ (Same Region, Private IP) $0.01/GB $0.01/GB $0.02/GB
Inter-Region (e.g., us-east-1 to us-west-2) $0.02/GB $0.00/GB $0.02/GB
Same AZ:
[ EC2 Instance A (AZ-1a) ] -------- (Free: $0.00/GB) --------> [ EC2 Instance B (AZ-1a) ]

Cross AZ:
[ EC2 Instance A (AZ-1a) ] -- $0.01/GB egress --> [ AZ Boundary ] -- $0.01/GB ingress --> [ EC2 Instance B (AZ-1b) ]
Total cost for point-to-point payload: $0.02/GB ($20 per TB)

Within the same AZ, private IP traffic costs nothing. Once a packet crosses the physical boundary to another AZ:

  1. The sending instance’s account is billed $0.01/GB for Inter-AZ Data Transfer Out.
  2. The receiving instance’s account is billed $0.01/GB for Inter-AZ Data Transfer In.

If Service A in us-east-1a sends a 1 GB payload to Service B in us-east-1b, and Service B returns a 1 GB response, you pay:

At scale, 50 TB of monthly cross-AZ traffic results in $1,000/month in pure internal routing overhead.

Monthly Cross-AZ Cost Scaling:
  10 TB cross-AZ payload  = $200 / month
  50 TB cross-AZ payload  = $1,000 / month
  100 TB cross-AZ payload = $2,000 / month
  500 TB cross-AZ payload = $10,000 / month

Where the terabytes vanish: three common culprits

1. Distributed datastores (Kafka, Cassandra, Elasticsearch)

Distributed storage systems replicate writes across nodes to guarantee fault tolerance. When nodes are distributed across three AZs for high availability, every write replicates across the AZ perimeter.

Consider an Apache Kafka cluster ingesting log and metrics streams:

Producer (AZ-a) 
   │
   ▼ (Local write: $0.00)
Leader Broker (AZ-a)
   ├─── Replicate to Follower (AZ-b) ───> 100 TB @ $0.01 egress + $0.01 ingress = $1,000
   └─── Replicate to Follower (AZ-c) ───> 100 TB @ $0.01 egress + $0.01 ingress = $1,000

Total Replication Cost: 200 TB cross-AZ traffic = $2,000/month

The 100 TB ingestion generates 200 TB of cross-AZ replication traffic ($2,000/month).

If downstream consumer applications running in us-east-1b and us-east-1c consume this data directly from the leader in us-east-1a without follower-fetching, that adds another 100 TB ($1,000/month) of cross-AZ consumer reads. A single pipeline moving 100 TB of application events can easily generate $3,000/month in inter-AZ network fees.

2. Kubernetes / EKS cross-AZ service meshes

In a standard Amazon EKS cluster spanning 3 AZs, Kubernetes assigns pods randomly across worker nodes unless configured with explicit topology constraints.

When Service A calls Service B through a standard ClusterIP Service or an internal Application Load Balancer (ALB), kube-proxy distributes connections uniformly via round-robin across all healthy endpoint pods regardless of zone.

In a 3-AZ cluster with evenly distributed pods:
- 33.3% of requests land on a pod in the SAME AZ (Free: $0.00)
- 66.7% of requests land on a pod in a DIFFERENT AZ ($0.02/GB)

In a microservice call chain where Request $\rightarrow$ Service A $\rightarrow$ Service B $\rightarrow$ Service C $\rightarrow$ Database:

Over millions of daily RPC requests transmitting JSON or gRPC payloads, two-thirds of your internal cluster bandwidth is billed at cross-AZ rates.

3. Remote cache lookups and chatty state stores

A common anti-pattern is deploying an in-memory caching tier (Redis or Memcached) as a single primary node in us-east-1a while application compute instances run in us-east-1a, us-east-1b, and us-east-1c using instances like c7g.xlarge.

If the application executes 20 cache queries per user request to assemble session data, permissions, and feature flags:

Four architectural fixes to eliminate cross-AZ waste

1. Kubernetes Topology Aware Routing

Kubernetes provides native support for keeping network traffic within the originating zone. Since Kubernetes 1.27+, Topology Aware Routing (enabled via service.kubernetes.io/topology-mode: Auto) instructs kube-proxy or the CNI plugin to prefer endpoints in the same zone as the calling client.

apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: production
  annotations:
    service.kubernetes.io/topology-mode: Auto
spec:
  selector:
    app: order-service
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

How it works:

  1. The EndpointSlice controller checks node labels (specifically topology.kubernetes.io/zone).
  2. It assigns endpoint subsets to each zone proportionally based on the allocatable vCPU capacity in that zone.
  3. When a pod in us-east-1a sends traffic to order-service, kube-proxy routes the request exclusively to pods running in us-east-1a.
  4. Cross-AZ routing only occurs during failover if no healthy local endpoints exist.

Enabling this annotation drops internal Kubernetes microservice cross-AZ traffic from ~67% down to near 0%.

2. AZ-aware Kafka routing (KIP-392 Fetch from Follower)

Historically, Kafka consumers could only read data from the partition leader broker. If the leader was in us-east-1a, a consumer in us-east-1b had to pull every message across the AZ boundary.

With Kafka 2.4+ and KIP-392, consumers can fetch messages directly from the closest in-sync replica (ISR) follower in their own AZ.

# 1. Broker configuration (server.properties)
# Map each broker to its AWS Availability Zone ID or Name
broker.rack=us-east-1a
replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector

# 2. Consumer client configuration
# Configure consumer instances with their local zone ID
client.rack=us-east-1a

When configured:

3. Co-locating chatty services with zone affinity

For high-throughput, low-latency microservice architectures, group services into independent, self-contained zone pools rather than letting calls cross AZ boundaries randomly.

Zone-Isolated Architecture:

AZ-1a: [ Ingress ALB ] ──> [ Web Pod ] ──> [ API Pod ] ──> [ Local Redis Replica ]
                                                                     ▲
                                                                     │ Async replication
AZ-1b: [ Ingress ALB ] ──> [ Web Pod ] ──> [ API Pod ] ──> [ Redis Primary (Leader) ]

Implementation guidelines:

4. Identifying top talkers with VPC Flow Logs and Contributor Insights

To locate which services are driving cross-AZ data transfer in your AWS environment, enable VPC Flow Logs with custom format fields that capture subnet and AZ metadata.

${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${subnet-id} ${az-id} ${pkt-src-aws-service} ${pkt-dst-aws-service}

CloudWatch Logs Insights Query:

Run this query in CloudWatch Logs Insights over your VPC Flow Log group to aggregate total transfer bytes between distinct subnets:

filter srcaddr not like /^169\.254\./ and dstaddr not like /^169\.254\./
| stats sum(bytes) as TotalBytes by srcaddr, dstaddr, subnetId
| sort TotalBytes desc
| limit 25

CloudWatch Contributor Insights Rule:

Create a Contributor Insights rule on VPC Flow Logs to produce real-time rankings of top IP pairs crossing AZs:

{
  "AggregateOn": "Sum",
  "Contribution": {
    "Filters": [
      {
        "Match": "action",
        "EqualTo": "ACCEPT"
      }
    ],
    "Keys": [
      "srcaddr",
      "dstaddr"
    ],
    "ValueOf": "bytes"
  },
  "LogFormat": "CLF",
  "LogGroupName": "/aws/vpc/production-flow-logs"
}

This report highlights which pod IPs, database endpoints, or broker instances generate the highest inter-AZ network volume. You can cross-reference instance families and compute sizing in the instance explorer to balance node densities per AZ.

Summary checklist

Area Default State Optimized State Estimated Savings
Kubernetes (EKS) Round-robin across all pods in all AZs (67% cross-AZ) Enable service.kubernetes.io/topology-mode: Auto 50%–65% reduction in cluster inter-service transfer fees
Kafka Streaming Consumers read only from partition leader Set client.rack + RackAwareReplicaSelector (KIP-392) 100% elimination of consumer cross-AZ transfer fees
Caching Tier Single Redis primary accessed by all AZs Local read replicas per AZ 60%–80% reduction in cache transfer fees + lower latency
Load Balancers Cross-zone load balancing enabled by default on ALBs Keep requests in-zone where ingress capacity is balanced $0.01/GB saved on incoming ingress hops

Audit your AWS bill for the DataTransfer-Regional-Bytes line item. If your monthly regional data transfer exceeds a few hundred dollars, cross-AZ traffic is the primary driver—and fixing it requires routing adjustments rather than tearing down your multi-AZ reliability.