Server

AWS VPC: Subnets, Route Tables, and What 'Public' Actually Means

By Prabath Thalangama· October 24, 2026· 4 min read
#aws#vpc#networking

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:

  1. 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.
  2. Public IP (public subnets only) — MapPublicIpOnLaunch or an EIP. Private subnets deliberately have none; they use the NAT.
  3. NAT Gateway state (private subnets) — is it available, does it have an EIP, and is it in a public subnet with an IGW route?
  4. Security group — outbound rules. The default SG allows all outbound; a locked-down one may not.
  5. 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.
  6. DNS — enableDnsSupport and enableDnsHostnames on 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/5432 from 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.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.