AWS VPC Basics: Subnets, Route Tables, and Gateways
Understand AWS VPC fundamentals: public vs private subnets, internet and NAT gateways, route tables, and the difference between security groups and NACLs.
What you'll learn
- ✓What a VPC is and how CIDR blocks are chosen
- ✓The difference between public and private subnets
- ✓How internet gateways and NAT gateways route traffic
- ✓How route tables steer packets between subnets and out to the internet
- ✓When to use security groups vs network ACLs
Prerequisites
- •Comfort with [What is AWS](/blog/what-is-aws)
- •A basic understanding of IP addressing and CIDR notation
- •Familiarity with [EC2 basics](/blog/aws-ec2-basics)
A Virtual Private Cloud (VPC) is your private slice of the AWS network. Every EC2 instance, RDS database, and Lambda function with VPC access runs inside one. Getting the VPC design right early saves you from painful migrations later, because changing IP ranges and subnet layouts on a busy account is genuinely hard.
This article walks through the core pieces — VPC, subnets, gateways, route tables, and the two firewalls — and stitches them into a working layout.
The VPC itself
A VPC is defined by a CIDR block, a range of private IP addresses you reserve for the network. Common choices come from RFC 1918:
10.0.0.0/16— 65,536 addresses, lots of room.172.16.0.0/16— useful if10.0.0.0/8is already taken by an on-prem network.192.168.0.0/16— small home networks; rarely used at scale on AWS.
Pick a block that does not overlap with anything you might want to peer with later — corporate networks, other VPCs, or VPN endpoints. A /16 is a reasonable default and gives you headroom to split into many subnets.
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications \
'ResourceType=vpc,Tags=[{Key=Name,Value=app-vpc}]'
A VPC lives in one region and spans all of its Availability Zones (AZs). Subnets are how you carve resources into specific AZs.
Subnets, public and private
A subnet is a slice of the VPC CIDR pinned to a single AZ. The distinction between public and private is not a checkbox — it is a property of how the subnet’s route table is configured.
- A public subnet has a route table entry that sends
0.0.0.0/0to an internet gateway, and resources in it are usually given public IPs. - A private subnet has no direct route to the internet. Outbound traffic, if allowed at all, goes through a NAT gateway.
A common pattern is three pairs of subnets across three AZs:
VPC 10.0.0.0/16
AZ a:
public 10.0.0.0/24
private 10.0.10.0/24
AZ b:
public 10.0.1.0/24
private 10.0.11.0/24
AZ c:
public 10.0.2.0/24
private 10.0.12.0/24
Place load balancers and bastion hosts in the public subnets, and put application servers and databases in the private subnets. This is the layout that supports highly available deployments.
Internet gateway vs NAT gateway
There are two gateways every team eventually meets:
- An internet gateway (IGW) is a horizontally scaled VPC component that enables bidirectional traffic between resources in a public subnet and the internet. It performs one-to-one NAT for instances that have a public IPv4 address. One IGW per VPC.
- A NAT gateway is a managed service that lives in a public subnet and gives private subnet resources outbound-only internet access. It translates the source IP so return traffic comes back to it, then forwards to the original instance. NAT gateways are billed per hour and per GB processed — meaningful at scale.
A common cost trap: deploying one NAT gateway and using it from all AZs. That works, but cross-AZ data transfer charges add up, and if the NAT’s AZ has an outage, every private subnet loses egress. The robust pattern is one NAT gateway per AZ.
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway --vpc-id vpc-123 --internet-gateway-id igw-456
aws ec2 allocate-address --domain vpc
aws ec2 create-nat-gateway --subnet-id subnet-public-a --allocation-id eipalloc-789
Route tables
A route table is a list of rules that decide where traffic with a given destination should go. Every subnet must be associated with exactly one route table; otherwise the VPC’s main route table is used.
A public subnet’s route table looks like:
Destination Target
10.0.0.0/16 local
0.0.0.0/0 igw-456
A private subnet’s route table looks like:
Destination Target
10.0.0.0/16 local
0.0.0.0/0 nat-abc
The local entry is the implicit rule that makes every subnet in the VPC reachable from every other subnet — you do not create it.
Other route targets you will see:
- VPC peering connections for cross-VPC traffic.
- Transit gateway attachments for hub-and-spoke topologies.
- VPN and Direct Connect gateways for on-prem connectivity.
- VPC endpoints for private connectivity to AWS services like S3 or DynamoDB without going through the internet.
Security groups vs NACLs
Two firewalls govern VPC traffic, and confusingly, they look similar at first glance. The differences matter.
| Feature | Security Group | Network ACL |
|---|---|---|
| Scope | Attached to ENIs (instances) | Attached to subnets |
| State | Stateful | Stateless |
| Rule types | Allow only | Allow and Deny |
| Evaluation | All rules evaluated | Rules evaluated in order |
| Default | Deny inbound, allow outbound | Allow inbound and outbound |
In practice:
- Use security groups as your primary firewall. They are stateful, so a rule that allows inbound on port 443 implicitly allows the response on the ephemeral port.
- Use NACLs for coarse subnet-level guardrails, like “no inbound traffic from this specific IP range, ever, no matter what the security groups say.”
Security groups can reference other security groups as a source, which is enormously powerful. Instead of writing IP ranges, you say “allow inbound 5432 from the app-sg security group.” When an app server is added to that group, it automatically gets database access — no IP juggling.
VPC endpoints
A small but important feature: VPC endpoints let resources in private subnets reach AWS services without going through a NAT gateway. There are two flavors:
- Gateway endpoints — free, available for S3 and DynamoDB. Added as a route table entry.
- Interface endpoints — billed per hour, available for most services. Implemented via PrivateLink ENIs.
Using a gateway endpoint for S3 alone can dramatically cut NAT gateway data processing charges in S3-heavy workloads.
A working blueprint
A solid baseline VPC for a small production workload:
VPC 10.0.0.0/16
Internet gateway attached
3 public subnets (one per AZ) -> route table: 0.0.0.0/0 -> IGW
3 private subnets (one per AZ) -> route table: 0.0.0.0/0 -> per-AZ NAT
S3 gateway endpoint -> attached to private route tables
Security groups:
alb-sg allow 443 from 0.0.0.0/0
app-sg allow 8080 from alb-sg
db-sg allow 5432 from app-sg
Load balancers sit in public subnets, app servers and RDS sit in private subnets, and outbound traffic from apps exits via the NAT gateways. Tear-down and re-creation is straightforward because the layout is symmetric across AZs.
Wrap up
A VPC has only a handful of moving parts, but the interactions between subnets, route tables, gateways, and firewalls drive almost every connectivity bug in AWS. Start with a clean CIDR plan, separate public and private subnets per AZ, use NAT gateways deliberately because of cost, and lean on security groups referencing security groups for clean east-west rules.
With this foundation in place, EC2, Lambda, and managed services slot in cleanly, and you have the network topology you need for a production-grade AWS account.