AWS VPC Networking Explained: Subnets, Route Tables, and NAT
VPC networking is where most cloud engineers' mental models break down. You can deploy applications for years on the default VPC without understanding what a route table does, until the day a Lambda in a private subnet can't reach the internet, or a database is accidentally public. This post builds the mental model from scratch, the way I teach it.
The VPC is just an address space
A VPC is a private slice of IP address space, defined by a CIDR block like 10.0.0.0/16:
about 65,000 addresses that belong to you. Nothing in it can be reached from outside until you
explicitly create a path. Every piece of VPC networking is about controlling those paths.
Subnets: public is a configuration, not a type
A subnet is a smaller CIDR range inside the VPC, tied to one Availability Zone. Here's the part that
unlocks everything: there is no "public subnet" checkbox. A subnet is public only
because its route table sends 0.0.0.0/0 to an Internet Gateway. A private subnet is just
one whose route table doesn't. The subnet itself is identical; the route table defines its nature.
Route tables: the actual decision-makers
Every subnet consults exactly one route table to decide where traffic goes. A typical pair looks like this:
- Public route table:
10.0.0.0/16 → local,0.0.0.0/0 → igw-xxxx(Internet Gateway). Resources here can be reached from the internet if they have a public IP. - Private route table:
10.0.0.0/16 → local,0.0.0.0/0 → nat-xxxx(NAT Gateway). Resources can reach out, but nothing can reach in.
When something "can't connect", the route table is the first place I look, before security groups, before DNS.
Internet Gateway vs NAT Gateway
The Internet Gateway is a two-way door: traffic in and out, for resources with public IPs. The NAT Gateway is a one-way door: instances in private subnets can initiate outbound connections (pulling packages, calling external APIs), but inbound connections are impossible. Two practical notes that bite people: a NAT Gateway must itself live in a public subnet, and it costs money per hour and per GB. An idle NAT Gateway in three AZs is a surprisingly common line item on the bill.
Security groups vs NACLs
- Security groups are stateful firewalls on the instance/ENI level. Allow inbound port 443 and the response traffic is automatically allowed back out. They only have allow rules. This is where 95% of your access control should live.
- NACLs are stateless filters at the subnet boundary. They evaluate rules in order, support deny rules, and require you to open ephemeral ports for return traffic. I leave them at their defaults unless I need a hard subnet-level block.
The three-tier layout I use by default
For production workloads, my starting template across two AZs:
- Public subnets: load balancers and NAT Gateways only. Nothing else.
- Private app subnets: ECS tasks, EC2 instances, Lambda ENIs. Outbound via NAT.
- Private data subnets: RDS and ElastiCache, with no route to the internet at all; their security groups accept traffic only from the app tier's security group.
Once you internalise that subnets are defined by their route tables and that security group references (not IP ranges) should chain your tiers together, VPC design stops being mysterious. Draw the route tables first; everything else follows.