Grab a coffee, open your AWS Cost Explorer, and let’s talk about the silent budget killer in your infrastructure: the AWS NAT Gateway tax.
If you run Elastic Kubernetes Service (EKS) in production, you have likely experienced this specific brand of financial whiplash. You glance at your monthly bill expecting EC2 compute or EBS storage to consume the bulk of your budget. Instead, `EC2-Other` is ballooning, dominated by two lines: NatGateway-Bytes charged at $0.045 per gigabyte, and DataTransfer-Regional-Bytes charged at $0.01 per gigabyte in each direction ($0.02/GB round-trip).
When microservices in your private subnets pull container images from EKS registries, upload application logs to CloudWatch, stream analytics to S3, or simply talk to pods sitting on nodes across availability zones, AWS charges you multiple times for the exact same byte of data. A single 500GB daily telemetry pipeline routed through a NAT Gateway back to S3 can quietly add over $800 a month to your bill—for data that never once needed to touch the public internet.
## How do you identify which EKS workloads are burning your egress budget?
Before altering your network architecture, you must attribute network flow costs down to specific namespaces, pods, and destination IP addresses.
The most effective method is running CloudWatch Insights or Amazon Athena queries against your VPC Flow Logs. The key is identifying internal AWS service IPs that are receiving traffic originating from your EKS worker node private subnets.
You can query your VPC Flow Logs in Amazon Athena using this query structure to isolate top outbound traffic talkers passing through your NAT Gateway interface:
“`sql
SELECT
srcaddr AS pod_or_node_ip,
dstaddr AS destination_ip,
SUM(bytes) / 1024 / 1024 / 1024 AS total_gb_transferred,
(SUM(bytes) / 1024 / 1024 / 1024) * 0.045 AS estimated_nat_processing_cost
FROM vpc_flow_logs
WHERE pkt_dstaddr NOT LIKE ’10.%’ — Exclude private RFC1918 traffic
AND pkt_dstaddr NOT LIKE ‘172.16.%’
AND pkt_dstaddr NOT LIKE ‘192.168.%’
AND log_status = ‘OK’
GROUP BY srcaddr, dstaddr
ORDER BY total_gb_transferred DESC
LIMIT 20;
“`
If your Kubernetes cluster uses an eBPF-based CNI like Cilium, you can skip flow log analysis entirely and extract real-time protocol-level egress metrics natively with `hubble`:
“`bash
hubble observe –namespace prod-analytics \
–to-cidr 0.0.0.0/0 \
–output json | jq -r ‘[.time, .src.pod_name, .destination.names[0] // .destination.ip, .summary] | @tsv’
“`
If the destination IPs trace back to AWS service endpoints (like S3 buckets, ECR registries, or Kinesis streams), you are burning money on NAT Gateway data processing fees for traffic that should never leave the AWS internal network core.
## What is the step-by-step engineering playbook to eliminate NAT Gateway fees?
To eliminate egress costs in an active production EKS environment without causing service downtime, follow this zero-downtime remediation workflow.
“`
+———————————————————————————–+
| EKS Cost Optimization Sequence |
| |
| [Step 1: Gateway Endpoints] —> [Step 2: Interface Endpoints] |
| (S3 / DynamoDB: Zero Cost) (ECR / CloudWatch: High Volume) |
| | |
| v |
| [Step 4: Self-Hosted fck-nat] <--- [Step 3: Topology Hints] |
| (Replace Managed NAT Gateways) (Eliminate Cross-AZ Hops) |
| |
| +---------------+ |
| | Step 5: IPv6 | |
| +---------------+ |
+-----------------------------------------------------------------------------------+
```
### Step 1: Deploy Zero-Cost Gateway Endpoints for S3 and DynamoDB
Gateway Endpoints are free VPC resources that inject direct routes into your subnet route tables for AWS S3 and DynamoDB. Traffic destined for these services bypasses the NAT Gateway entirely and routes straight over the AWS internal network backbone.
Here is how to declare an S3 Gateway VPC Endpoint using Terraform:
```hcl
resource "aws_vpc_endpoint" "s3_gateway" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.aws_region}.s3"
vpc_endpoint_type = "Gateway"
# Attach to all private subnet route tables used by EKS worker nodes
route_table_ids = var.private_route_table_ids
tags = {
Name = "eks-s3-gateway-endpoint"
Environment = "production"
}
}
```
Once applied, AWS updates your subnet route tables with a prefix list entry (e.g., `pl-63a5400a`). Any pod attempting to reach an S3 bucket instantly routes through the direct endpoint. Cost savings: 100% of data processing fees for S3 traffic.
### Step 2: Provision Strategic Interface Endpoints (AWS PrivateLink)
For services that do not support Gateway Endpoints—such as ECR (`ecr.api` and `ecr.dkr`), CloudWatch Logs (`logs`), Systems Manager (`ssm`), or Security Token Service (`sts`)—you can deploy Interface Endpoints powered by AWS PrivateLink.
Interface Endpoints cost ~$0.01 per hour per AZ plus a reduced data processing fee of $0.01 per GB (compared to NAT Gateway’s $0.045 per GB).
#### The Financial Break-Even Math
Deploying an Interface Endpoint across 3 AZs costs ~$22.00/month in baseline hourly costs. Because Interface Endpoints save $0.035 per GB ($0.045 NAT fee minus $0.01 Interface fee), the endpoint becomes net-profitable the moment a single AWS service processes more than 630 GB of data per month.
Example Terraform snippet for ECR Container Registry endpoints:
“`hcl
resource “aws_vpc_endpoint” “ecr_dkr” {
vpc_id = var.vpc_id
service_name = “com.amazonaws.${var.aws_region}.ecr.dkr”
vpc_endpoint_type = “Interface”
private_dns_enabled = true
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
tags = {
Name = “eks-ecr-dkr-endpoint”
}
}
“`
*Note: You must ensure `private_dns_enabled = true` is set so that the EKS cluster automatically resolves internal domain calls (e.g., `*.dkr.ecr.us-east-1.amazonaws.com`) to the private IP addresses of the endpoint ENIs rather than public IPs.*
### Step 3: Prevent Cross-AZ Traffic with Kubernetes Topology-Aware Routing
By default, the Kubernetes `kube-proxy` component distributes pod traffic randomly across all available endpoints matching a Service, regardless of which Availability Zone the receiving pod resides in. This behavior routinely generates massive inter-AZ data transfer fees ($0.02/GB round-trip).
To force Kubernetes to prefer endpoints located in the same zone as the calling pod, enable Topology-Aware Hints (or Topology-Aware Routing).
Add the following annotation to your internal Kubernetes `Service` manifests:
“`yaml
apiVersion: v1
kind: Service
metadata:
name: payment-processor-service
namespace: production
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: payment-processor
ports:
– protocol: TCP
port: 8080
targetPort: 8080
“`
When using Cilium as your cluster network plugin, enforce strict node-local and zone-local routing in your `Helm` value configuration:
“`yaml
cilium:
loadBalancer:
algorithm: maglev
routingMode: native
locality:
routing:
enabled: true
“`
Enabling locality-aware routing keeps microservice-to-microservice traffic local to the originating AZ, eliminating intermediate network hops across Availability Zone boundaries.
### Step 4: Replace Managed NAT Gateways with `fck-nat`
If your Kubernetes cluster requires outbound access to the general internet (e.g., third-party APIs, webhooks, or package repositories) where VPC Endpoints cannot be used, standard AWS NAT Gateways become an expensive bottleneck.
For low-to-moderate internet egress workloads (< 5 Gbps), you can replace managed NAT Gateways with `fck-nat`—an open-source, AMI-optimized NAT daemon engineered to run on low-cost Graviton (`t4g.nano` or `t4g.micro`) EC2 instances.
#### Cost Comparison Breakdown
* AWS Managed NAT Gateway: ~$32.40/month baseline per AZ + $0.045/GB.
* `fck-nat` on `t4g.micro` Spot Instance: ~$2.50/month baseline per AZ + $0.00/GB data processing fee.
To deploy `fck-nat`, deploy an Autoscaling Group running the `fck-nat` AMI attached to your private subnet route tables as the primary `0.00.0/0` default gateway target:
“`hcl
resource “aws_instance” “fck_nat” {
ami = data.aws_ami.fck_nat.id
instance_type = “t4g.micro”
# Crucial: NAT instances must have source/destination checking disabled
source_dest_check = false
subnet_id = aws_subnet.public_az_a.id
vpc_security_group_ids = [aws_security_group.nat_instance.id]
associate_public_ip_address = true
tags = {
Name = “fck-nat-gateway-az-a”
}
}
resource “aws_route” “private_nat_route” {
route_table_id = aws_route_table.private_az_a.id
destination_cidr_block = “0.0.0.0/0”
network_interface_id = aws_instance.fck_nat.primary_network_interface_id
}
“`
For high-availability configurations, couple `fck-nat` with an Elastic IP and launch script that automatically re-attaches the ENI upon instance lifecycle events.
### Step 5: The Architectural Endgame: Migrate to Dual-Stack IPv6
The ultimate long-term remedy for NAT Gateway expense is eliminating Network Address Translation altogether. In an IPv6-enabled VPC, every pod receives a globally unique, routable IPv6 address.
Outbound communication over IPv6 does not use a NAT Gateway. Instead, it uses an Egress-Only Internet Gateway (EIGW). An EIGW allows outbound traffic from private subnets to the public internet while preventing unsolicited inbound connections from reaching your pods.
#### Key Advantage
AWS charges $0.00 for Egress-Only Internet Gateways. There are no hourly fees and zero data processing fees. You pay only standard AWS internet egress transfer rates ($0.09/GB sliding down with scale).
To enable dual-stack IPv6 networking on EKS:
1. Enable an IPv6 CIDR block on your AWS VPC.
2. Configure the EKS cluster with `–ip-family=ipv6`.
3. Provision an Egress-Only Internet Gateway:
“`hcl
resource “aws_egress_only_internet_gateway” “eigw” {
vpc_id = var.vpc_id
tags = {
Name = “eks-ipv6-eigw”
}
}
resource “aws_route” “private_ipv6_egress” {
route_table_id = aws_route_table.private_subnets.id
destination_ipv6_cidr_block = “::/0”
egress_only_gateway_id = aws_egress_only_internet_gateway.eigw.id
}
“`
When pods initiate connections to external services over IPv6, packets route directly through the EIGW, completely bypassing the NAT processing layer.
## What common implementation pitfalls ruin NAT Gateway optimization?
Engineers often implement VPC Endpoints or topology hints only to find their AWS bill remains completely unchanged. Watch out for these operational traps:
### 1. Forgetting to Update Private Route Tables
Creating an S3 Gateway Endpoint provisions the resource, but if you do not attach all private route tables used by your EKS node groups, traffic will continue falling through to the `0.0.0.0/0` default route heading directly into your NAT Gateway.
### 2. Disabling Private DNS on Interface Endpoints
If `private_dns_enabled` is set to `false` during Interface Endpoint creation, public DNS queries inside your EKS pods for `ecr.us-east-1.amazonaws.com` will still resolve to AWS public IP addresses. Your pods will continue sending traffic to the public internet via your NAT Gateway. Ensure your cluster’s CoreDNS configuration respects private VPC DNS resolution.
### 3. Asymmetric Cross-AZ NAT Gateway Placement
To save money on hourly charges, teams sometimes provision a single NAT Gateway in `us-east-1a` and point subnets in `us-east-1b` and `us-east-1c` to it. While this saves ~$64/month in NAT Gateway hourly fees, it forces all outbound traffic from subnets `b` and `c` to cross Availability Zones, triggering a $0.02/GB cross-AZ tax on top of the standard $0.045/GB NAT processing tax. Always keep NAT resources localized per AZ or use interface endpoints.
—
### Frequently Asked Questions
#### How do I know if my EKS cluster is routing S3 traffic through a NAT Gateway?
Review your VPC Flow Logs or query CloudWatch Log Insights for your NAT Gateway’s Network Interface ID (ENI). Filter for destination IP addresses belonging to the AWS S3 IP address range (obtainable via `https://ip-ranges.amazonaws.com
Direct Practical Verdict:
## Why is your AWS NAT Gateway bill higher than your compute bill?
To fix NAT Gateway costs, you must understand the three distinct layers of AWS networking tolls. When a pod inside a private EKS subnet makes an outbound network call, AWS evaluates the traffic route and applies a cascading stack of fees depending on the destination and physical topology.
“`
+———————————————————————————–+
| EKS Node (Subnet AZ-a) |
| +——————-+ +——————–+ +——————+ |
| | Pod A (App Log) | | Pod B (Image Pull) | | Pod C (Telemetry)| |
| +———+———+ +———+———-+ +——–+———+ |
+————|—————————-|—————————-|————+
| | |
[Cross-AZ Egress] [Cross-AZ Egress] [VPC Gateway Endpoint]
$0.01/GB Out $0.01/GB Out $0.00/GB
| | |
v v v
+————————+ +————————+ +————————-+
| NAT Gateway (AZ-b) | | Interface Endpoint | | AWS S3 Bucket |
| $0.045/hr + $0.045/GB | | (ECR / CloudWatch) | | (Free In-Region Traffic)|
+————+———–+ | $0.01/hr + $0.01/GB | +————————-+
| +————————+
v
[Internet / API]
“`
### 1. The Fixed Hourly Provisioning Toll
AWS charges $0.045 per hour for every managed NAT Gateway you provision. In a standard multi-AZ architecture spanning three Availability Zones, running three NAT Gateways for high availability costs a baseline of $97.20 per month before a single byte of data passes through them.
### 2. The Data Processing Surcharge
This is where bills compound aggressively. AWS charges $0.045 per GB for all data processed through a NAT Gateway, regardless of whether that data is going to a public API, external partner, or an internal AWS service like S3 or CloudWatch. If your cluster processes 10 Terabytes of egress data per month, you pay $450.00 purely for the privilege of packet translation.
### 3. The Hidden Cross-AZ Egress Penalty
If a pod sitting on a node in `us-east-1a` sends outbound traffic through a NAT Gateway deployed in `us-east-1b`, AWS levies an additional inter-AZ data transfer charge of $0.01 per GB outbound plus $0.01 per GB inbound ($0.02/GB total). If your Kubernetes cluster relies on a single NAT Gateway shared across multiple private subnets to save on the hourly provisioning fee, you inadvertently double down on cross-AZ transport costs.
The following matrix outlines how different network routing patterns impact your monthly infrastructure budget:
To eliminate AWS NAT Gateway egress costs in EKS, deploy free VPC Gateway Endpoints for S3 and DynamoDB, configure VPC Interface Endpoints for high-volume AWS APIs, enable Topology-Aware Routing to prevent cross-AZ traffic charges, replace managed NAT Gateways with open-source fck-nat instances for low-to-medium workloads, or migrate to dual-stack IPv6 to eliminate NAT entirely.
| Routing Strategy | Hourly Cost (per AZ) | Data Processing Fee | Cross-AZ Egress Fee | Primary Financial & Use Case Fit |
|---|---|---|---|---|
| AWS Managed NAT Gateway | $0.045 / hour | $0.045 / GB | $0.02 / GB (if cross-AZ) | Default baseline. Expensive for high-throughput public outbound traffic. |
| Gateway VPC Endpoints | Free ($0.00) | Free ($0.00) | $0.00 (same AZ) | Mandatory for S3 and DynamoDB. Zero cost regardless of throughput volume. |
| Interface VPC Endpoints | ~$0.01 / hour | $0.01 / GB | $0.02 / GB (if cross-AZ) | Ideal for internal AWS APIs (ECR, CloudWatch, STS) moving >250GB/mo. |
| fck-nat (EC2 Instance) | ~$0.0042 / hr (t4g.nano) | Free ($0.00) | $0.02 / GB (if cross-AZ) | Slashes NAT costs by up to 85% for low-to-moderate Internet outbound traffic. |
| IPv6 Dual-Stack + EIGW | Free ($0.00) | Free ($0.00) | $0.00 (direct path) | Architectural endgame. Completely removes NAT layer for IPv6 destinations. |
Cloud Networking, Egress Optimization & Cost Architecture
Object storage request volumes and inter-availability-zone traffic represent prime cost leakages in distributed architectures. Read our analysis on AWS S3 Express One Zone: FinOps Latency & Cost.
