Subnets, Route Tables & Gateways
Subnets, Route Tables & Gateways
A VPC by itself is an empty address space. Subnets, route tables, and gateways are the three mechanisms that carve that space into functional network segments and control how traffic flows in, out, and between them. Getting this topology right is the single most impactful architectural decision in an AWS account — mistakes here cause security incidents, unexpected data-transfer bills, and multi-hour outages.
Subnets: Carving the VPC
A subnet is a contiguous block of IP addresses within your VPC CIDR, confined to a single Availability Zone. Every EC2 instance, RDS instance, Lambda VPC attachment, and ECS task runs inside a subnet. The subnet is where AZ-level fault isolation actually lives.
The canonical production layout uses three tiers across at least two AZs:
- Public subnets — hosts that must be directly reachable from the internet (load balancers, NAT gateways, bastion hosts). Each public subnet has auto-assign public IPv4 enabled and is associated with a route table that has a default route to an Internet Gateway.
- Private subnets — application tier (ECS tasks, EC2 app servers, Lambda). No public IP. Outbound internet access goes through a NAT Gateway sitting in the public subnet of the same AZ.
- Isolated (data) subnets — RDS, ElastiCache, OpenSearch. No route to the internet at all — not even NAT. Traffic is VPC-internal or flows through VPC Endpoints only.
CIDR Sizing Rules
AWS reserves 5 addresses per subnet (network, VPC router, DNS, future, broadcast). A /28 gives only 11 usable IPs — fine for a subnet holding one NAT Gateway, too small for anything else. A /24 gives 251 usable. Most teams allocate /20 blocks for public and private, /24 for isolated. Never resize a subnet after launch — plan for 3× your expected peak and use non-overlapping RFC 1918 ranges if you intend to peer or connect to on-premises networks.
Internet Gateways (IGW)
An Internet Gateway is a horizontally scaled, fully managed VPC component that enables bidirectional IPv4/IPv6 traffic between your VPC and the public internet. There is exactly one IGW per VPC. Attaching it costs nothing; data-transfer pricing applies to outbound traffic. An IGW does two things:
- Acts as the target for the default route (
0.0.0.0/0 → igw-xxxxxxxx) in a public subnet's route table. - Performs 1:1 NAT between an instance's private IP and its Elastic IP (or auto-assigned public IP) for instances that have one.
NAT Gateways
Private instances often need to reach the internet for package updates, pulling container images, or calling external APIs — but you do not want them exposed inbound. A NAT Gateway (NGW) solves this. It lives in a public subnet, holds an Elastic IP, and performs port-address translation for outbound connections from private instances. AWS manages the gateway entirely — no patching, no instance sizing, automatic scaling to 100 Gbps.
Critical operational properties:
- NAT Gateways are AZ-scoped. Route each private subnet to the NAT Gateway in the same AZ to avoid cross-AZ data transfer charges and AZ-dependency failures.
- They are one-way: private instances can initiate connections out; nothing can initiate connections in through NAT.
- They do not support inbound connections or port-forwarding.
- Cost model: $0.045/hr (us-east-1) + $0.045/GB processed. Three NAT Gateways (one per AZ) in a busy environment can run $200–$400/month — keep that in the cost model from day one.
Route Tables
A route table is an ordered list of destination-prefix → target rules. Every subnet must be associated with exactly one route table. If you do not explicitly associate one, the subnet uses the VPC's main route table — which is fine for isolated subnets, risky for everything else because changes to the main table affect every implicitly associated subnet.
A minimal three-tier setup needs at minimum:
- Public RT —
10.0.0.0/16 → local(auto-created) +0.0.0.0/0 → igw-xxx - Private RT (per AZ) —
10.0.0.0/16 → local+0.0.0.0/0 → nat-xxx-in-same-AZ - Isolated RT —
10.0.0.0/16 → localonly
Routes are matched longest-prefix first. The local route (your VPC CIDR) is always implicitly present and cannot be deleted — it ensures all subnets can reach each other by default.
Infrastructure as Code: Terraform Example
In production, every VPC component is defined in Terraform. The following excerpt creates one public subnet, one private subnet, an IGW, a NAT Gateway, and their route tables in a single AZ. A real module loops this across multiple AZs.
Verifying the Layout with AWS CLI
After applying, verify route tables and their associations before deploying workloads:
VPC Architecture Diagram
Production Failure Modes to Know
Missing depends_on for NAT Gateway. In Terraform, if the NAT Gateway resource is created before the IGW is attached to the VPC, AWS returns an error because NAT requires an IGW to exist first. Always set depends_on = [aws_internet_gateway.main] on the NAT Gateway resource.
Route table not associated. A freshly created route table has no subnet associations. Terraform's aws_route_table_association resource (or aws_subnet.route_table_id in CDK) performs the binding. Without it, the subnet silently falls back to the main route table.
NAT Gateway in wrong subnet. NAT Gateways must reside in a public subnet. Placing one in a private subnet creates a circular dependency — the private subnet's route points to NAT, NAT itself has no internet path, so all outbound traffic silently drops.
Tier=public, Tier=private, Tier=isolated and AZ=us-east-1a enable cost explorer breakdowns by tier, Security Hub findings by tier, and fast manual auditing. Make this a required tag in your AWS Organizations SCP from day one.