AWS Route Tables Explained: Public, Private, NAT & IGW
A route table is a set of rules: each entry maps a destination to a next hop, telling the VPC router where to send traffic leaving a subnet. It is the single mechanism that makes a subnet public, private, or isolated. Get the route table right and your network topology works. Get it wrong and traffic silently goes nowhere, or to the wrong place entirely.
Simple explanation
A route table is a list where each row says: traffic going to destination X should be forwarded to next-hop Y.
Destination Target
10.0.0.0/16 local ← traffic to VPC addresses stays inside
0.0.0.0/0 igw-0abc1234 ← everything else goes to the internetThink of a route table like the sorting rules at a post office. Every parcel (packet) has a destination address, and the sorter (VPC router) checks the rules from most specific to least specific to decide which truck (target) it goes on. A parcel addressed to 10.0.3.5 goes on the local internal van. A parcel addressed to anything else goes on the internet truck. Change which truck handles unknown destinations and you change the entire character of the post office.
That second entry, 0.0.0.0/0 → igw, is what makes a subnet public. Change it to 0.0.0.0/0 → nat-gw and the subnet becomes private with outbound-only internet access. Remove the default route entirely and the subnet is isolated; traffic only flows within the VPC.
Every subnet uses exactly one route table. That route table is what determines the subnet’s relationship with the outside world.
How route tables work
When a packet leaves a resource in a subnet, the VPC router follows this sequence:
- Look up the route table associated with that subnet.
- Compare the packet’s destination IP against every route entry in the table.
- Select the most specific matching route: the one with the longest prefix (highest slash number).
- Forward the packet to that route’s target.
Route tables only control where traffic goes, not whether it is allowed. A route to an internet gateway does not open any ports or permit any traffic. Security groups and network ACLs handle that separately. A common beginner mistake is adding a route and expecting traffic to flow, when a security group is still blocking it.
Example: a route table with two entries:
10.0.0.0/16 local
0.0.0.0/0 igw-0abc1234Traffic to 10.0.3.5 matches the local route (more specific than /0) and stays inside the VPC. Traffic to 203.0.113.1 only matches 0.0.0.0/0 and routes out through the IGW.
If a route table also contains 10.0.5.0/24 → pcx-peering alongside 10.0.0.0/16 → local, traffic to 10.0.5.100 uses the /24 peering route because it is more specific. This is longest prefix matching. AWS always picks the route with the highest prefix length when multiple entries match the same destination.
Main route table vs custom route tables
When you create a VPC, AWS automatically creates a main route table. Every subnet without an explicit custom route table association falls back to this table.
If you add a 0.0.0.0/0 → IGW route to the main route table, every subnet without an explicit association quietly becomes publicly routable — including your application servers and databases. This is one of the most common VPC misconfigurations in AWS. Keep the main route table clean (local route only) and build separate custom route tables for each subnet tier.
| Route Table | Typical subnets | Default route target | Purpose |
|---|---|---|---|
| Main route table | Any subnet with no explicit association | None (local only) | Safety net; kept clean to prevent accidental internet exposure |
| Public route table | Web tier, ALB, bastion hosts | Internet Gateway (IGW) | Full inbound and outbound internet traffic |
| Private route table | App servers, Lambda, ECS tasks | NAT Gateway | Outbound-only internet access; no inbound from internet |
| Isolated route table | Databases, internal services | None (local only) | Completely cut off from the internet in both directions |
How route tables make subnets public, private, or isolated
The presence, absence, and destination of the default route (0.0.0.0/0) is what defines a subnet’s network character.
Public subnet: the route table has
0.0.0.0/0 → IGW. Resources can receive inbound connections from the internet, provided they also have a public IP or Elastic IP and their security group allows the inbound traffic.Private subnet: the route table has
0.0.0.0/0 → NAT Gateway. Resources can initiate outbound connections (package downloads, external API calls), but inbound connections from the internet cannot reach them. The NAT Gateway sits in a public subnet and handles the address translation.Isolated subnet: the route table has no default route at all. Resources can only communicate within the VPC. Used for databases and internal services that should never touch the internet, not even for outbound traffic.
A public route alone does not expose an instance to the internet. For inbound traffic to reach an EC2 instance, all three of these must be in place:
- The subnet’s route table has
0.0.0.0/0 → IGW. - The instance has a public IP address or Elastic IP.
- The security group has an inbound rule allowing the traffic on the right port.
Miss any one of these and the instance is unreachable from the internet.
Common route targets
The target in a route entry tells the VPC router where to forward matched traffic. These are the targets you will encounter most often:
- local: built-in and immutable. Routes all traffic within the VPC’s CIDR block to stay inside the VPC. Present in every route table and cannot be removed.
- Internet Gateway (igw-xxx): routes traffic to and from the public internet. Required in any public subnet’s route table. You must attach the IGW to the VPC first.
- NAT Gateway (nat-xxx): routes outbound internet traffic from private subnets through a managed NAT device that lives in a public subnet. Full details in NAT Gateway.
- VPC Peering Connection (pcx-xxx): routes traffic to a peered VPC’s CIDR range. Both sides of a VPC peering connection must have matching route entries pointing to each other’s CIDR.
- Transit Gateway (tgw-xxx): routes traffic through a central hub connecting multiple VPCs, VPN connections, and Direct Connect attachments. The primary tool for large-scale hybrid network architectures.
- Virtual Private Gateway (vgw-xxx): used for Site-to-Site VPN and Direct Connect connections. Routes can be added statically or propagated automatically via BGP.
- Gateway Endpoint: routes traffic to S3 or DynamoDB without leaving the AWS network, bypassing the NAT Gateway entirely. Reduces cost and eliminates internet exposure for those services.
Route tables vs security groups vs network ACLs
Route tables, security groups, and network ACLs are three separate controls that operate at different layers. Beginners frequently confuse them or assume that one makes the others unnecessary.
| Control | What it does | Operates at | Stateful? | Can block traffic? |
|---|---|---|---|---|
| Route table | Decides where traffic is forwarded | Subnet level | N/A (routing only) | No; routes or silently drops (no matching route) |
| Security group | Allows or denies traffic at an instance | Instance (ENI) level | Yes; return traffic is automatically allowed | Yes; default-deny inbound, allow-all outbound |
| Network ACL | Allows or denies traffic at a subnet boundary | Subnet level | No; return traffic needs an explicit allow rule | Yes; supports explicit deny rules |
The practical takeaway: a route table can enable internet access for a subnet, but it cannot protect an instance from unauthorized traffic. Security groups are the primary instance-level defense. Network ACLs add a subnet-level layer on top, most useful when you need explicit deny rules such as blocking a known bad IP range.
When to use this
You need to review or modify route tables whenever you are:
- Creating a new subnet tier. Every new subnet needs a deliberate route table association. Skipping this means it inherits the main route table, which may not match your intent.
- Connecting subnets to the internet. Public-facing resources need a subnet with a
0.0.0.0/0 → IGWroute. Private subnets that need outbound internet access need a0.0.0.0/0 → NAT Gatewayroute. - Routing between VPCs. VPC peering and Transit Gateway both require route entries pointing to the connection ID. Without them, peered VPCs cannot communicate even if the peering connection is active.
- Setting up hybrid connectivity. Site-to-Site VPN and Direct Connect traffic needs routes pointing to a Virtual Private Gateway or Transit Gateway. These can be added statically or enabled via BGP route propagation.
- Optimizing with gateway endpoints. S3 and DynamoDB traffic from private subnets can bypass NAT Gateway using a gateway endpoint, but only if you add the corresponding prefix list route to the relevant private route tables.
- Debugging connectivity failures. When an instance cannot reach a destination, the route table is one of the first places to check. See Troubleshooting Network Issues for a systematic approach.
3-tier architecture walkthrough
Most production VPCs use a three-tier pattern: a public web tier, a private application tier, and an isolated data tier. Here is how route tables map to that layout across three Availability Zones.
| Tier | Subnet type | Route table | Default route | Why |
|---|---|---|---|---|
| Public web tier | Public subnets (one per AZ) | One shared public route table | 0.0.0.0/0 → IGW | ALBs and bastion hosts must be reachable from the internet |
| Private app tier | Private subnets (one per AZ) | One private route table per AZ | 0.0.0.0/0 → NAT-GW (same AZ) | App servers need outbound internet for packages and APIs; no inbound connections allowed |
| Isolated data tier | Isolated subnets (one per AZ) | One shared isolated route table | None | Databases have no valid reason to initiate or accept internet traffic |
Each AZ in the private tier gets its own NAT Gateway and its own route table. If one AZ’s NAT Gateway fails, the private subnets in the other AZs keep their outbound internet access through their own local NAT Gateways. Sharing one NAT Gateway across all AZs is a common cost-cutting choice that creates a cross-AZ single point of failure.
CLI examples
# Public subnet route table
aws ec2 create-route-table \
--vpc-id vpc-0123456789abcdef0 \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-rt}]'
aws ec2 create-route \
--route-table-id rtb-0123456789abcdef0 \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id igw-0123456789abcdef0
aws ec2 associate-route-table \
--route-table-id rtb-0123456789abcdef0 \
--subnet-id subnet-public-0123456789abcdef0# Private subnet route table (repeat per AZ with that AZ's NAT Gateway)
aws ec2 create-route-table \
--vpc-id vpc-0123456789abcdef0 \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=private-rt-1a}]'
aws ec2 create-route \
--route-table-id rtb-private-0123456789abcdef0 \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id nat-0123456789abcdef0
aws ec2 associate-route-table \
--route-table-id rtb-private-0123456789abcdef0 \
--subnet-id subnet-private-0123456789abcdef0Terraform example
# Public route table: shared across all public subnets
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "public-rt" }
}
resource "aws_route_table_association" "public_1a" {
subnet_id = aws_subnet.public_1a.id
route_table_id = aws_route_table.public.id
}
# Private route table: one per AZ for high availability
resource "aws_route_table" "private_1a" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.az1a.id
}
tags = { Name = "private-rt-1a" }
}
resource "aws_route_table_association" "private_1a" {
subnet_id = aws_subnet.private_1a.id
route_table_id = aws_route_table.private_1a.id
}Common mistakes
- Most common: polluting the main route table
Adding
0.0.0.0/0 → igwto the main route table quietly makes every subnet without an explicit association publicly routable, including your application servers and databases. Always keep the main route table clean and use custom route tables for every subnet. Forgetting to associate a subnet with the correct route table. Creating a subnet does not assign it to your custom route table. It falls back to the main table. Always explicitly associate every new subnet before deploying resources into it.
Assuming a route to the IGW is sufficient for inbound internet access. A route is necessary but not sufficient. The instance also needs a public IP and an inbound security group rule. All three must be in place.
Using one NAT Gateway for all Availability Zones. A single NAT Gateway in us-east-1a serves private subnets across all AZs, but if us-east-1a has an outage, outbound internet breaks for every private subnet simultaneously. Create one NAT Gateway per AZ and point each AZ’s private route table at its local NAT Gateway.
Not checking for more specific routes when debugging. A default route pointing to the right target does not mean all traffic uses it. A more specific entry such as
10.0.5.0/24 → pcx-peeringoverrides the default for matching addresses. When traffic behaves unexpectedly, inspect the full route table for specific routes before assuming the default route is the issue.
Summary
- Route tables map destinations to next hops. Every subnet uses exactly one route table.
- The local route is immutable and keeps VPC-internal traffic inside the VPC.
0.0.0.0/0 → IGW= public subnet.0.0.0.0/0 → NAT-GW= private subnet. No default route = isolated subnet.- Route tables control routing only. Security groups and NACLs handle access filtering.
- A public route alone does not expose an instance. A public IP and a security group rule are also required.
- AWS applies the most specific matching route (longest prefix). Keep this in mind when debugging.
- Keep the main route table clean. Assign every subnet to a custom route table explicitly.
- In multi-AZ deployments, create one NAT Gateway per AZ with its own private route table for true high availability.
Frequently asked questions
What is the "local" route and can I remove it?
The local route (e.g., 10.0.0.0/16 → local) keeps all traffic between subnets inside the VPC. AWS creates it automatically in every route table and you cannot delete or modify it. It is the reason resources in different subnets can communicate without any extra routing configuration.
Can a subnet be associated with multiple route tables?
No. Each subnet uses exactly one route table at a time. However, one route table can be shared by multiple subnets. If a subnet has no explicit association, it falls back to the VPC main route table.
Does a route to an IGW automatically make an EC2 instance reachable from the internet?
No. Three things must all be true: the subnet route table must have a 0.0.0.0/0 route to an IGW, the instance must have a public IP or Elastic IP, and the security group must allow the relevant inbound traffic. Missing any one of the three keeps the instance unreachable.
What wins if two routes match the same packet?
AWS uses the most specific route: the one with the longest prefix (highest number after the slash). A /24 route beats a /16 route for matching addresses. If two routes have identical prefix length, a static route takes precedence over a route propagated from a BGP source.
What is the difference between the main route table and a custom route table?
The main route table is created automatically with every VPC. Any subnet without an explicit association uses it by default. A custom route table is one you create and explicitly associate with specific subnets. Best practice is to keep the main route table clean (local route only) and assign every subnet to a purpose-built custom route table.