Nearly every resource that isn't fully serverless runs in a VPC, and networking mistakes are the most common cause of 'it can't connect'.
A VPC is an isolated virtual network in one Region with an IPv4 CIDR block between /16 and /28 (for example 10.20.0.0/16), optionally plus IPv6. Plan CIDRs that don't overlap with other VPCs or your offices, because overlapping ranges can't be peered or routed together later.
A subnet is a slice of the VPC's range in one Availability Zone. AWS reserves five addresses in every subnet (network, router, DNS, a future-use address and broadcast), so a /24 gives 251 usable IPs. Create subnets in at least two AZs.
Every subnet is associated with a route table. Routes are matched by longest prefix, and every table has a local route for the VPC CIDR, so all subnets can reach each other by default.
What makes a subnet public is only its routes:
- Public subnet: a route
0.0.0.0/0to an Internet Gateway (IGW). Instances there also need a public IP to be reachable. - Private subnet: no route to the IGW. Instances can't be reached from the internet; for outbound access they use a NAT gateway.
The standard layout is three tiers across two or three AZs: public subnets for load balancers and NAT gateways, private subnets for applications, and isolated subnets for databases.
Every account has a default VPC per Region with public subnets, handy for experiments but not for production. Use VPC Flow Logs to see accepted and rejected traffic when debugging.
Example
VPC=$(aws ec2 create-vpc --cidr-block 10.20.0.0/16 --query Vpc.VpcId --output text)
PUB=$(aws ec2 create-subnet --vpc-id "$VPC" --cidr-block 10.20.0.0/24 \
--availability-zone eu-west-1a --query Subnet.SubnetId --output text)
PRIV=$(aws ec2 create-subnet --vpc-id "$VPC" --cidr-block 10.20.10.0/24 \
--availability-zone eu-west-1a --query Subnet.SubnetId --output text)
IGW=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --vpc-id "$VPC" --internet-gateway-id "$IGW"
# A route table with a default route to the IGW makes a subnet public
RT=$(aws ec2 create-route-table --vpc-id "$VPC" --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id "$RT" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW"
aws ec2 associate-route-table --route-table-id "$RT" --subnet-id "$PUB"
aws ec2 describe-subnets --subnet-ids "$PUB" --query 'Subnets[0].AvailableIpAddressCount'
# 251Remember
- A subnet lives in exactly one AZ; AWS reserves 5 IPs in each
- Public vs private is decided by the route table (route to an IGW or not)
- Choose non-overlapping CIDRs up front
- Spread tiers across at least two AZs
Watch out for
- Picking a tiny or overlapping VPC CIDR, which can't be fixed without rebuilding.