Courses Quizzes Articles Contact
Login
AWS VPC Step 3 of 14 · Easy · What a VPC is made of
TECH

Route tables and the local route

Karen Created Oct 13, 2026 Updated Oct 13, 2026 4 min read

A subnet does not decide where packets go. Its route table does. Every packet leaving an instance is matched against that table, and the most specific matching row wins. Inside the VPC, one row is always present and you do not write it: the local route. This lesson is that row, the rule that picks among rows, and what happens when nothing matches.

One subnet, one table

Each subnet is associated with exactly one route table. Many subnets may share a table. An instance cannot pick a different table for itself. If the application and the database need different paths, they need different subnets.

When you create a VPC, AWS creates a main route table and puts the local route in it. Any subnet you do not explicitly associate uses the main table. That default is the usual accident: someone adds an internet route to the main table “just to test,” and every subnet they forgot to associate, including the database, starts using it.

Create a table per tier and associate every subnet with one of them. Leave the main table alone, with only the local route AWS put there. A subnet with no explicit association is then still private.

The local route

For a VPC of 10.0.0.0/16, the local route is:

  • Destination 10.0.0.0/16
  • Target local

The VPC router delivers any packet whose address falls in that range. An instance in 10.0.1.0/24 reaches an instance in 10.0.12.0/24 because both addresses are inside 10.0.0.0/16, not because you added a route for each subnet. The return packet uses the destination subnet’s table, which has the same local route. Both directions have to match. One table is not enough if the other subnet’s table cannot route back.

You cannot delete this route, and you cannot replace it with a more specific route that sends VPC addresses out a gateway. Addresses inside the CIDR stay on the VPC router. Terraform will not show the local route in the resource. An empty aws_route_table still has it.

A route is not permission. The local route means the packet can be delivered. The security group on the destination still has to allow it. Two instances that “should see each other” and do not are often allowed by the route and denied by the group. That distinction comes back in a later lesson. Do not “fix” it by adding routes.

The most specific row wins

When several rows match, AWS uses the longest prefix. A /24 beats a /16, and a /16 beats 0.0.0.0/0. 0.0.0.0/0 matches every address, so it is only used when no narrower row matches.

Packet to Rows that exist Row that wins
10.0.12.20 10.0.0.0/16 → local, and maybe 0.0.0.0/0 local. The address is inside the VPC, and that route cannot be overridden.
10.1.4.8 10.1.0.0/16 → peering, and 0.0.0.0/0 → igw The /16. It is narrower than the default route, so this packet does not go to the internet.
1.1.1.1 Only 10.0.0.0/16 → local No row matches. The packet is dropped.
1.1.1.1 local and 0.0.0.0/0 → igw The default route. Nothing more specific claimed this address.

The peering row is here only as a prefix-length example. Connecting two VPCs is a later lesson. The internet gateway row is the next lesson after this one. What matters now is the third case: a table with only the local route drops everything else. That is a private subnet. It is not broken.

A table with no extra routes

This is the private tier from the address plan. The resource has no route block. Association is a separate resource. Forgetting the association leaves the subnet on the main table, which is why the main table should stay empty of extra routes.

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id

  tags = { Name = "private-rt" }
}

resource "aws_route_table_association" "private" {
  for_each       = aws_subnet.private
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private.id
}

After apply, the table in the console shows 10.0.0.0/16 → local even though the file does not. An instance in private-a can reach private-b and the public subnets. It cannot reach 1.1.1.1. Adding that path is a NAT gateway or a public address, and both are later lessons.

When the main table is the wrong place for a route

  • You added 0.0.0.0/0 to the main table. Every subnet without an explicit association inherited the internet. Move that route to a table named for the public tier, and associate only the public subnets with it.
  • Two tiers share one table. You cannot give the database a different default route without moving it to another subnet.
  • A connection inside the VPC fails and the route table is blamed. Check the local route is there (it is), then check the security group. Adding a more specific route for 10.0.12.0/24 will be rejected or will not change delivery.

A private subnet is a subnet whose table has only the local route. Traffic to other addresses in the VPC is delivered. Traffic to anywhere else is dropped until you add a row.

You're reading a free lesson from AWS VPC

The rest of the lessons are not for sale yet.