Overview
NAT (Network Address Translation) gateways enable instances in private subnets to access the internet while preventing inbound connections from the internet. The module supports two NAT gateway deployment modes:
Multiple NAT Gateways - One NAT gateway per availability zone (high availability)
Single NAT Gateway - One shared NAT gateway across all AZs (cost-optimized)
Configuration Variables
Controls whether NAT gateways should be provisioned for private networks. Default: falseDefined in: variables.tf:58-61
Controls whether to provision a single shared NAT gateway across all private networks instead of one per AZ. Default: falseDefined in: variables.tf:63-66
NAT gateways require public subnets to exist, as they must be deployed in public subnets with internet gateway access.
How NAT Gateways Work
Elastic IP Allocation
From main.tf:111-115, Elastic IPs are allocated for NAT gateways:
resource "aws_eip" "nateip" {
count = " ${ var . enable_nat_gateway ? (var . single_nat_gateway ? 1 : length (var . azs )) : 0 } "
vpc = true
}
The count logic:
If enable_nat_gateway = false: 0 EIPs created
If single_nat_gateway = true: 1 EIP created
If single_nat_gateway = false: EIPs created equal to number of AZs
NAT Gateway Creation
From main.tf:117-124, NAT gateways are created in public subnets:
resource "aws_nat_gateway" "natgw" {
count = " ${ var . enable_nat_gateway ? (var . single_nat_gateway ? 1 : length (var . azs )) : 0 } "
allocation_id = " ${ element (aws_eip . nateip .*. id , (var . single_nat_gateway ? 0 : count . index )) } "
subnet_id = " ${ element (aws_subnet . public .*. id , (var . single_nat_gateway ? 0 : count . index )) } "
depends_on = [ "aws_internet_gateway.mod" ]
}
Private Route Configuration
From main.tf:35-41, routes to NAT gateways are added to private route tables:
resource "aws_route" "private_nat_gateway" {
count = " ${ var . enable_nat_gateway ? length (var . azs ) : 0 } "
route_table_id = " ${ element (aws_route_table . private .*. id , count . index ) } "
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = " ${ element (aws_nat_gateway . natgw .*. id , count . index ) } "
}
Deployment Modes
Multiple NAT Gateways (One Per AZ) This configuration provides high availability by deploying a NAT gateway in each availability zone. module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "ha-vpc"
cidr = "10.0.0.0/16"
azs = [
"us-east-1a" ,
"us-east-1b" ,
"us-east-1c"
]
private_subnets = [
"10.0.1.0/24" ,
"10.0.2.0/24" ,
"10.0.3.0/24"
]
public_subnets = [
"10.0.101.0/24" ,
"10.0.102.0/24" ,
"10.0.103.0/24"
]
# Create NAT gateway in each AZ
enable_nat_gateway = true
single_nat_gateway = false
tags = {
Environment = "production"
}
}
Result:
3 NAT gateways created (one per AZ)
3 Elastic IPs allocated
Each private subnet routes through its own AZ’s NAT gateway
High availability: AZ failure doesn’t affect other AZs
Cost: ~0.045 / h o u r p e r N A T g a t e w a y + d a t a p r o c e s s i n g c h a r g e s = 0.045/hour per NAT gateway + data processing charges = ~ 0.045/ h o u r p er N A T g a t e w a y + d a t a p rocess in g c ha r g es = 97.20/month for 3 NAT gatewaysSingle Shared NAT Gateway This configuration reduces costs by using one NAT gateway shared across all availability zones. module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "dev-vpc"
cidr = "10.0.0.0/16"
azs = [
"us-west-2a" ,
"us-west-2b" ,
"us-west-2c"
]
private_subnets = [
"10.0.1.0/24" ,
"10.0.2.0/24" ,
"10.0.3.0/24"
]
public_subnets = [
"10.0.101.0/24" ,
"10.0.102.0/24" ,
"10.0.103.0/24"
]
# Create single shared NAT gateway
enable_nat_gateway = true
single_nat_gateway = true
tags = {
Environment = "development"
}
}
Result:
1 NAT gateway created (in first public subnet)
1 Elastic IP allocated
All private subnets route through the same NAT gateway
Lower cost but single point of failure
Cost: ~0.045 / h o u r + d a t a p r o c e s s i n g c h a r g e s = 0.045/hour + data processing charges = ~ 0.045/ h o u r + d a t a p rocess in g c ha r g es = 32.40/monthFully Isolated Private Subnets For workloads that don’t require internet access. module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "isolated-vpc"
cidr = "10.0.0.0/16"
azs = [ "eu-west-1a" , "eu-west-1b" ]
private_subnets = [
"10.0.1.0/24" ,
"10.0.2.0/24"
]
# No NAT gateway - fully isolated
enable_nat_gateway = false
# Use VPC endpoints for AWS services
enable_s3_endpoint = true
enable_dynamodb_endpoint = true
tags = {
Environment = "secure"
}
}
Result:
No NAT gateways created
No internet access from private subnets
Use VPC endpoints to access AWS services
Cost: $0 for NAT gateways
Architecture Diagrams
Multiple NAT Gateways Architecture
┌─────────────────────────────────────────────────────────────┐
│ VPC │
│ 10.0.0.0/16 │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ us-east-1a │ │ us-east-1b │ │ us-east-1c │ │
│ │ │ │ │ │ │ │
│ │ Public Subnet │ │ Public Subnet │ │ Public Subnet │ │
│ │ 10.0.101.0/24 │ │ 10.0.102.0/24 │ │ 10.0.103.0/24 │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ NAT GW 1 │ │ NAT GW 2 │ │ NAT GW 3 │ │
│ │ │ │ │ │ │ │ │ │ │
│ └──────┼────────┘ └──────┼────────┘ └──────┼────────┘ │
│ │ │ │ │
│ ┌──────▼────────┐ ┌──────▼────────┐ ┌──────▼────────┐ │
│ │ │ │ │ │ │ │
│ │Private Subnet │ │Private Subnet │ │Private Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │ 10.0.3.0/24 │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Single NAT Gateway Architecture
┌─────────────────────────────────────────────────────────────┐
│ VPC │
│ 10.0.0.0/16 │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ us-east-1a │ │ us-east-1b │ │ us-east-1c │ │
│ │ │ │ │ │ │ │
│ │ Public Subnet │ │ Public Subnet │ │ Public Subnet │ │
│ │ 10.0.101.0/24 │ │ 10.0.102.0/24 │ │ 10.0.103.0/24 │ │
│ │ │ │ │ │ │ │ │
│ │ NAT GW │ │ │ │ │ │
│ │ │ │ │ │ │ │ │
│ └──────┼────────┘ └───────────────┘ └───────────────┘ │
│ │ │ │ │
│ ┌──────▼────────┐ ┌──────▼────────┐ ┌──────▼────────┐ │
│ │ └───────┼──┼───────┘ └──┼───────┘ │ │
│ │Private Subnet │ │Private Subnet │ │Private Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │ 10.0.3.0/24 │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Use Cases
Production Environments
module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "production-vpc"
cidr = "10.0.0.0/16"
azs = [ "us-east-1a" , "us-east-1b" , "us-east-1c" ]
private_subnets = [ "10.0.1.0/24" , "10.0.2.0/24" , "10.0.3.0/24" ]
public_subnets = [ "10.0.101.0/24" , "10.0.102.0/24" , "10.0.103.0/24" ]
# High availability NAT configuration
enable_nat_gateway = true
single_nat_gateway = false # One NAT gateway per AZ
tags = {
Environment = "production"
Criticality = "high"
}
}
Development/Testing Environments
module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "dev-vpc"
cidr = "10.1.0.0/16"
azs = [ "us-west-2a" , "us-west-2b" ]
private_subnets = [ "10.1.1.0/24" , "10.1.2.0/24" ]
public_subnets = [ "10.1.101.0/24" , "10.1.102.0/24" ]
# Cost-optimized NAT configuration
enable_nat_gateway = true
single_nat_gateway = true # Single shared NAT gateway
tags = {
Environment = "development"
Criticality = "low"
}
}
Secure Database Tier
module "vpc" {
source = "github.com/terraform-community-modules/tf_aws_vpc"
name = "secure-database-vpc"
cidr = "10.2.0.0/16"
azs = [ "eu-central-1a" , "eu-central-1b" , "eu-central-1c" ]
database_subnets = [ "10.2.21.0/24" , "10.2.22.0/24" , "10.2.23.0/24" ]
# No NAT gateway - databases are fully isolated
enable_nat_gateway = false
# Use VPC endpoints for AWS service access
enable_s3_endpoint = true
create_database_subnet_group = true
tags = {
Environment = "production"
Tier = "database"
}
}
Cost Considerations
NAT Gateway Pricing (as of 2024):
Hourly charge: ~$0.045 per NAT gateway per hour
Data processing: ~$0.045 per GB processed
Monthly cost per NAT gateway: ~$32.40 (hourly) + data processing
Example Monthly Costs:
Single NAT gateway: ~$32-50/month
Three NAT gateways: ~$97-150/month
Data transfer costs vary by usage
Cost Optimization Strategies
Use Single NAT Gateway for non-production environments
Leverage VPC Endpoints to avoid data processing charges for S3/DynamoDB traffic
Consider NAT Instances for very low-traffic scenarios (not supported by this module)
Monitor Data Transfer to identify optimization opportunities
Best Practices
Production Recommendations:
✓ Use single_nat_gateway = false for high availability
✓ Deploy NAT gateways in all availability zones
✓ Monitor NAT gateway metrics (BytesOutToDestination, BytesInFromSource)
✓ Use VPC endpoints to reduce NAT gateway data processing costs
Common Mistakes:
Enabling NAT gateway without public subnets (deployment fails)
Using single NAT gateway in production (single point of failure)
Not considering data processing costs for high-traffic workloads
Forgetting that database and ElastiCache subnets share private route tables
Troubleshooting
Private Instances Can’t Access Internet
Check:
enable_nat_gateway = true is set
Public subnets exist and have internet gateway routes
Private subnet route tables have routes to NAT gateway (main.tf:35-41)
Security groups allow outbound traffic
Network ACLs allow outbound traffic
High NAT Gateway Costs
Solutions:
Enable S3 VPC endpoint to avoid S3 traffic through NAT gateway
Enable DynamoDB VPC endpoint for DynamoDB traffic
Review CloudWatch metrics to identify high-traffic sources
Consider AWS PrivateLink for other AWS services
Single NAT Gateway Failure
Impact:
All private subnets lose internet access
Cross-AZ data transfer charges for subnets in other AZs
Solution:
# Upgrade to high availability
single_nat_gateway = false
Subnet Types Configure private, public, and database subnets
VPC Endpoints Reduce NAT gateway costs with VPC endpoints