A NAT gateway lets resources in a private subnet reach the internet without exposing that subnet to unsolicited inbound connections. In a typical cloud network, the gateway belongs to the public portion of the network, while private-subnet traffic reaches it through routing. section 10.2.3
A Terraform network commonly connects the pieces this way:
- Give the public subnet public IP addresses and place the NAT gateway there. section 10.2.3
- Create an Elastic IP for the NAT gateway. section 10.2.3
- Route internet-bound traffic from the private subnet toward the NAT gateway. section 10.2.3
- Ensure the internet gateway exists before creating the NAT gateway, using depends_on when Terraform cannot infer the dependency. section 5.7.3
Public and private subnet module
A module can accept a VPC ID, CIDR block, and availability zone; derive public and private CIDR ranges with cidrsubnet; create the public subnet with map_public_ip_on_launch enabled; and attach a NAT gateway and private-subnet route table. This arrangement provides outbound internet access to the private subnet while keeping the isolated subnet without internet access when that third network is added. section 10.2.3
Listing 10.6 Public and private subnet module
variable "cidr_block" {
description = "The CIDR block for the network."
type = string
}
variable "vpc_id" {
description = "The ID of the VPC."
type = string
}
variable "availability_zone" {
description = "The availability zone."
type = string
}
locals {
private_subnet_cidr_block = cidrsubnet(var.cidr_block, 1, 0) #1
public_subnet_cidr_block = cidrsubnet(var.cidr_block, 1, 1) #1
}
#
# Public Network
#
resource "aws_subnet" "public" {
vpc_id = var.vpc_id
cidr_block = local.public_subnet_cidr_block #2
map_public_ip_on_launch = true #3
availability_zone = var.availability_zone
tags = {
Network = "Public" #4
}
}
resource "aws_eip" "nat" {
tags = {}
}
resource "aws_nat_gateway" "main" { #5
allocation_id = aws_eip.nat.id #6
subnet_id = aws_subnet.public.id #7
tags = {}
}
#
# Private Network
#
resource "aws_subnet" "private" { #8
vpc_id = var.vpc_id
cidr_block = local.private_subnet_cidr_block #9
availability_zone = var.availability_zone #10
tags = {
Network = "Private"
}
}
resource "aws_route_table" "private" { #11
vpc_id = var.vpc_id
}
resource "aws_route_table_association" "private" { #12
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
resource "aws_route" "internet_access" { #13
route_table_id = aws_route_table.private.id
nat_gateway_id = aws_nat_gateway.main.id
destination_cidr_block = "0.0.0.0/0" #14
}
output "private_subnet_id" { #15
value = aws_subnet.private.id
}
output "public_subnet_id" { #16
value = aws_subnet.public.id
}
Inspect the Terraform listing for the module inputs, CIDR subdivision, public subnet settings, Elastic IP, and NAT gateway resource. section 10.2.3

