AWS VPC: Subnets, Route Tables, and What 'Public' Actually Means
Introduction¶
"Public subnet" and "private subnet" aren't AWS resource types — they're a consequence of routing. Once you see how the pieces connect, the common failures ("my EC2 instance times out downloading packages", "my RDS is unreachable") become quick to diagnose.
Concepts¶
A VPC is a private IPv4 (and optionally IPv6) address range, say
10.0.0.0/16. You carve it into subnets, each tied to one Availability
Zone: 10.0.1.0/24 in az-a, 10.0.2.0/24 in az-b, and so on.
Traffic leaving a subnet is directed by its route table. Every route table has an implicit local route for the VPC CIDR (instances can always reach each other). What you add on top decides everything else:
| Destination | Target | Effect |
|---|---|---|
0.0.0.0/0 |
Internet Gateway (igw-...) |
Subnet is public — instances with a public IP can talk to the internet both ways |
0.0.0.0/0 |
NAT Gateway (nat-...) |
Subnet is private — instances can initiate outbound, but nothing outside can start a connection in |
0.0.0.0/0 |
(no route) | Subnet is isolated — no internet at all |
So a "public subnet" is just a subnet whose route table sends 0.0.0.0/0 to an
IGW. Nothing else.
Two more rules:
- An instance also needs a public IP (auto-assigned or an Elastic IP) to use an IGW. A public route table + no public IP = still no internet.
- The NAT Gateway itself lives in a public subnet and needs an Elastic IP. Private subnets route to the NAT; the NAT routes to the IGW.
Hands-on¶
A minimal two-tier layout¶
VPC 10.0.0.0/16
├── subnet public-a 10.0.0.0/24 rt: 0.0.0.0/0 -> igw
├── subnet public-b 10.0.1.0/24 rt: 0.0.0.0/0 -> igw
├── subnet private-a 10.0.10.0/24 rt: 0.0.0.0/0 -> nat-a
└── subnet private-b 10.0.11.0/24 rt: 0.0.0.0/0 -> nat-b
A load balancer and NAT gateways go in the public subnets; app servers and databases go in the private subnets. Put a NAT per AZ so an AZ failure doesn't take out the other AZ's outbound traffic (and to avoid cross-AZ data charges).
Check routing from the CLI¶
# Which route table is a subnet using?
aws ec2 describe-route-tables \
--filters "Name=association.subnet-id,Values=subnet-0abc123" \
--query "RouteTables[].Routes"
# Every subnet and whether it auto-assigns public IPs
aws ec2 describe-subnets \
--query "Subnets[].{Id:SubnetId,Cidr:CidrBlock,AZ:AvailabilityZone,PublicIP:MapPublicIpOnLaunch}" \
--output table
Route precedence¶
Routes are matched most-specific-first. A route for 10.0.5.0/24 -> pcx-...
(a peering connection) wins over 0.0.0.0/0 -> nat-... for that range. This is
how you send some traffic to a VPN or peered VPC while everything else goes to
NAT.
Verification and troubleshooting¶
Instance can't reach the internet — check in this order:
- Route table — does the associated table have
0.0.0.0/0? A subnet with no explicit association uses the VPC's main route table, which is often private by default. - Public IP (public subnets only) —
MapPublicIpOnLaunchor an EIP. Private subnets deliberately have none; they use the NAT. - NAT Gateway state (private subnets) — is it
available, does it have an EIP, and is it in a public subnet with an IGW route? - Security group — outbound rules. The default SG allows all outbound; a locked-down one may not.
- Network ACL — subnet-level, stateless. Both an outbound and a
matching inbound rule (for the return traffic's ephemeral ports,
1024–65535) are needed. NACLs are the most-forgotten layer. - DNS —
enableDnsSupportandenableDnsHostnameson the VPC if you're resolving public names.
Other symptoms:
- RDS "could not connect" — RDS in a private subnet is correct. Connect from
an instance in the same VPC, or via a bastion / SSM port forward. Check the
RDS security group allows
3306/5432from the app SG (reference the SG, not a CIDR). - Works from one AZ, not another — you only created a NAT in one AZ, or the second private subnet's route table points at a NAT in a different AZ that you later deleted.
- Intermittent timeouts under load through NAT — port exhaustion. A single NAT gateway supports ~55,000 simultaneous connections per destination; spread load or add NATs.
Related tools and reading¶
- On-site: Subnet Calculator, IP Lookup, Port Checker.
- Related posts: Security groups vs NACLs, EC2 instance metadata and IMDSv2.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Cloud & AWS
AWS WAF: The Rules That Matter
AWS WAF filters HTTP requests at the ALB/CloudFront/API Gateway edge. Here's a sensible baseline Web ACL, how to tune with count mode, and the false-positive traps.
October 24, 2026An S3 Static Site Behind CloudFront
The right way to host a static site on AWS: a private S3 bucket, CloudFront with OAC in front, HTTPS via ACM, and SPA fallback routing. Plus the 403s that trip everyone.
October 24, 2026CloudWatch Logs and Metric Filters
CloudWatch Logs is cheap to write and expensive to forget about. Here's retention, turning error patterns into metrics and alarms, and Logs Insights for ad-hoc queries.
October 24, 2026