BlogCloud & Infrastructure
Cloud & Infrastructure

Eliminating AWS NAT Gateway Costs: VPC Endpoints, PrivateLink, and IPv6

NAT Gateway charges $0.045/GB processed β€” and it silently bills you for every byte your EC2 instances send to S3, ECR, and AWS services. VPC Endpoints eliminate that cost entirely for eligible services. This guide cuts your NAT Gateway bill to near zero.

M

Marcus Rodriguez

Lead DevOps Engineer specializing in CI/CD pipelines, container orchestration, and infrastructure automation.

April 10, 2026
18 min read

Why NAT Gateway Bills Are Shocking

NAT Gateway pricing has two components: $0.045/hour per NAT Gateway (about $32/month) and $0.045/GB of data processed. The hourly cost is predictable. The data processing cost is not.

Consider a Kubernetes cluster pulling Docker images from ECR during deployments. Each 500MB image pull Γ— 50 pods deploying daily = 25GB/day = 750GB/month of NAT Gateway traffic = $33.75/month per cluster β€” on top of the $32/month for the NAT Gateway itself, on top of the ECR data transfer fees. For a company running 10 clusters, that is $657/month just from image pulls.

The solution is VPC Endpoints, which route traffic from your VPC directly to AWS services without going through NAT Gateway at all.

VPC Gateway Endpoints (Free)

# Gateway endpoints are FREE β€” no per-GB charge
# Supported services: S3 and DynamoDB ONLY

# Terraform: Create S3 and DynamoDB Gateway Endpoints
resource "aws_vpc_endpoint" "s3" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.us-east-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [
    aws_route_table.private_a.id,
    aws_route_table.private_b.id,
    aws_route_table.private_c.id,
  ]

  policy = jsonencode({
    Statement = [{
      Action    = "s3:*"
      Effect    = "Allow"
      Resource  = "*"
      Principal = "*"
    }]
  })

  tags = { Name = "s3-endpoint" }
}

resource "aws_vpc_endpoint" "dynamodb" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.us-east-1.dynamodb"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = [
    aws_route_table.private_a.id,
    aws_route_table.private_b.id,
    aws_route_table.private_c.id,
  ]
  tags = { Name = "dynamodb-endpoint" }
}

# After creating these, ALL S3 and DynamoDB traffic from private subnets
# automatically routes through the endpoint β€” zero NAT Gateway charges.

VPC Interface Endpoints (Paid but Worth It)

# Interface endpoints cost $0.01/hr per AZ = $7.20/month per AZ
# But eliminate NAT processing fees for all covered services

# High-value interface endpoints for most environments:
resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ecr.api"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ecs" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ecs"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "secretsmanager" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.secretsmanager"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ssm" {
  vpc_id              = aws_vpc.main.id
  service_name        = "com.amazonaws.us-east-1.ssm"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_security_group" "vpc_endpoints" {
  name   = "vpc-endpoints"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.main.cidr_block]
  }
}

Cost Break-Even Analysis for Interface Endpoints

Interface Endpoint Cost vs NAT Gateway Savings:

Setup: 3 AZs, 1 endpoint per service
Endpoint cost (ECR API + ECR DKR + ECS = 3 endpoints Γ— 3 AZs):
  9 Γ— $0.01/hr Γ— 720hr = $64.80/month

NAT Gateway savings from ECR traffic:
  Assume 10 ECS tasks Γ— 1GB image Γ— 2 deploys/day Γ— 30days = 600GB/month
  NAT saving: 600GB Γ— $0.045 = $27/month ... not enough

Better scenario (50 pods Γ— 500MB Γ— 3 deploys/day Γ— 30days):
  = 2,250GB/month NAT saving: 2,250 Γ— $0.045 = $101.25/month
  Endpoint cost: $64.80/month
  Net saving: $36.45/month

Rule of thumb: Interface endpoints pay off when:
  Monthly data volume through endpoint > (endpoint_count Γ— AZ_count Γ— $7.20) / $0.045
  For 3 endpoints Γ— 3 AZs: > $64.80 / $0.045 = 1,440 GB/month

For high-traffic production clusters (EKS, ECS):
  Typically 5,000-50,000+ GB/month through ECR/S3 β†’ big savings
  Smaller clusters: gateway endpoints (S3, DynamoDB) are always worth it

Finding Your Current NAT Gateway Data Processing Costs

# Check NAT Gateway data processing costs in Cost Explorer
aws ce get-cost-and-usage   --time-period Start=2024-01-01,End=2024-02-01   --granularity MONTHLY   --filter '{
    "And": [
      {"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Virtual Private Cloud"]}},
      {"Dimensions": {"Key": "USAGE_TYPE_GROUP", "Values": ["VPC: NAT Gateway - Data Processed"]}}
    ]
  }'   --metrics "BlendedCost"   --query 'ResultsByTime[0].Total.BlendedCost'

# Find which VPCs have NAT Gateways
aws ec2 describe-nat-gateways   --query 'NatGateways[?State=='available'].{ID:NatGatewayId,VPC:VpcId,AZ:SubnetId}'   --output table

# Check if S3/DynamoDB gateway endpoints already exist
aws ec2 describe-vpc-endpoints   --filters "Name=vpc-endpoint-type,Values=Gateway"   --query 'VpcEndpoints[*].{Service:ServiceName,State:State,VPC:VpcId}'

IPv6: Eliminating NAT Gateway Entirely

# IPv6 public addresses are free and don't need NAT Gateway
# EKS nodes with IPv6 can reach the internet directly

# Enable IPv6 on VPC
resource "aws_vpc" "main" {
  cidr_block                       = "10.0.0.0/16"
  assign_generated_ipv6_cidr_block = true
  enable_dns_hostnames             = true
  enable_dns_support               = true
}

resource "aws_subnet" "private_a" {
  vpc_id                          = aws_vpc.main.id
  cidr_block                      = "10.0.1.0/24"
  ipv6_cidr_block                 = cidrsubnet(aws_vpc.main.ipv6_cidr_block, 8, 1)
  assign_ipv6_address_on_creation = true  # All ENIs get IPv6 automatically
  availability_zone               = "us-east-1a"
}

# Egress-only internet gateway (IPv6 equivalent of NAT β€” but FREE)
resource "aws_egress_only_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  route {
    ipv6_cidr_block        = "::/0"
    egress_only_gateway_id = aws_egress_only_internet_gateway.main.id
  }
  
  # Keep NAT for IPv4 external services that don't have IPv6
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id
  }
}

# EKS IPv6 cluster (fully dual-stack)
resource "aws_eks_cluster" "main" {
  name     = "production"
  role_arn = aws_iam_role.eks.arn

  kubernetes_network_config {
    ip_family         = "ipv6"
    service_ipv6_cidr = null  # AWS assigns automatically
  }

  vpc_config {
    subnet_ids = aws_subnet.private[*].id
  }
}

Summary: NAT Gateway Cost Reduction Playbook

Action                          | Cost        | Saving           | Effort
--------------------------------|-------------|------------------|--------
S3 gateway endpoint             | FREE        | All S3 NAT fees  | 10 min
DynamoDB gateway endpoint       | FREE        | All DDB NAT fees | 10 min
ECR interface endpoints         | $21.60/mo   | Image pull fees  | 30 min
Secrets Manager endpoint        | $21.60/mo   | Secret fetch fees| 30 min
SSM endpoint                    | $21.60/mo   | SSM API fees     | 30 min
Remove unused NAT Gateways      | -$32/mo each| Direct saving    | 30 min
IPv6 for internal services      | FREE        | Most IPv6 traffic| 1-2 days
Single NAT per region (dev)     | -$64/mo     | Direct saving    | 1 hour

Typical result for a mid-size EKS cluster:
  Before: $800/month (NAT hourly + data processing)
  After gateway endpoints + ECR: $180/month
  Monthly saving: $620 (77%)

Conclusion

NAT Gateway costs are one of the most impactful quick wins in AWS cost optimization. Gateway endpoints for S3 and DynamoDB are completely free and take 10 minutes to deploy β€” there is no reason not to have them. Interface endpoints for ECR pay for themselves in almost every EKS or ECS production environment.

After implementing all the strategies above, most organizations reduce NAT Gateway spend by 60-80%. The remaining NAT traffic is for external internet access that genuinely needs NAT, which is a legitimate cost you cannot eliminate without rearchitecting.

M

Marcus Rodriguez

Lead DevOps Engineer specializing in CI/CD pipelines, container orchestration, and infrastructure automation.

Ready to Transform Your Infrastructure?

Let's discuss how we can help you achieve similar results.