Cloud Networking

Route Tables

Destination → target, evaluated by longest prefix match, attached to a subnet. Three columns and a handful of rows that decide whether anything in your network can reach anything at all — and whose failure mode is always a hang.

The question this answers

Infrastructure question

When a packet leaves a workload, what decides where it goes next — and why does the wrong answer produce silence instead of an error?

Application requirement

The application in the private tier must reach the database inside the network, the payment API on the internet, an object store without paying for a NAT, and a partner network reached over a transit hub. Four destinations, four different next hops, one table.

What it provides

An explicit, reviewable statement of every destination a subnet can reach and by which path — which is simultaneously the connectivity design and the exposure audit.

Application RequirementInfrastructure RequirementComputeNetworkStorageIdentityDeploymentScalingReliabilityObservabilitySecurityCostTrade-offs

Three columns, and the row that is always there

provider-specific· Prefix-list and endpoint route syntax is AWS-flavored here; the destination-to-target model is identical everywhere.

A route table is a list of destination prefixes and the target each one is sent to. Every table starts with a local route covering the network's own range, which is why every subnet reaches every other subnet with no configuration at all — a fact that surprises people who expect subnet boundaries to be security boundaries. They are not. The local route is not removable, and segmentation inside the network is the job of security groups and network ACLs, not of routing.

Selection is longest prefix match, exactly as the Computer Networking domain teaches: the most specific matching prefix wins regardless of the order rows are written. 10.20.0.0/16 → local and 0.0.0.0/0 → nat coexist happily because traffic to 10.20.5.7 matches the /16 and traffic to a public address matches only the default route. A more specific route to a private endpoint or a peered network overrides the default without touching it.

The attachment matters as much as the contents. A table governs the subnets associated with it, so "the route exists" is only half the check — the other half is whether *this* subnet is associated with *that* table. Adding a route to the wrong table, or leaving a subnet on the default table, is one of the most common causes of a workload that cannot reach something everything else can.

ROUTE TABLE  rtb-private-a        associated subnets: private-a, data-a

  DESTINATION          TARGET                    WHY IT IS THERE
  10.20.0.0/16         local                     always present; every subnet in the network
  0.0.0.0/0            nat-gateway-a             egress for package and API calls, outbound only
  198.51.100.0/24      pcx-partner               partner network over peering, non-transitive
  10.90.0.0/16         tgw-corp                  corporate range via the transit hub
  pl-objectstore       vpce-objectstore          private endpoint: never touches the NAT meter

Longest prefix wins. Traffic to 10.20.5.7 -> local. Traffic to 10.90.4.2 -> tgw-corp.
Everything else -> nat-gateway-a.


ROUTE TABLE  rtb-public-a         associated subnets: public-a

  10.20.0.0/16         local
  0.0.0.0/0            igw-main                  this single row is what makes the subnet public
The private tier's route table. ILLUSTRATIVE addresses.

The five targets, and what each one implies

The target column is short, and each entry commits you to something. Sending the default route to an internet gateway makes the subnet public and its resources reachable if they hold public addresses. Sending it to a NAT device gives outbound-only connectivity and a per-gigabyte meter. Sending a prefix to a peering connection joins two networks point-to-point, and — the detail that catches everyone — peering is not transitive, so A↔B and B↔C does not give A↔C. A transit hub exists precisely to fix that, at the price of a billed attachment per network and a per-gigabyte charge on top.

A private endpoint route is the quiet win. Pointing provider-service traffic at an endpoint keeps it inside the provider network: lower latency, no NAT capacity consumed, no NAT data-processing charge. Object storage is the canonical case, and the traffic volume involved means this frequently changes the bill visibly. See Private Connectivity.

Whatever the target, the failure signature is the same. A packet with no matching route, or a route to a target that cannot deliver, is dropped. Nothing sends a rejection, so the client waits for its TCP timeout. That is why a routing mistake presents as a hang while a wrong port presents as an immediate connection refused — and why the first question in any reachability incident should be which of those two symptoms you actually have.

TargetWhat traffic it carriesDirectionWhat it costs
localEverything inside the network's own range.BothNothing. Unremovable — which is why subnets are not security boundaries.
Internet gatewayTo and from the public internet, for resources with public addresses.BothNo hourly charge; data transfer out is metered. Makes the subnet public.
NAT deviceOutbound-initiated traffic from private resources.Outbound onlyHourly per device per zone, plus every gigabyte processed. See NAT Gateway.
Peering connectionTraffic to one specific other network.BothPer-gigabyte transfer. Non-transitive, so the mesh grows quadratically.
Transit hubTraffic to many networks and on-premises through one attachment.BothPer attachment-hour and per gigabyte. Fixes the mesh; adds a shared dependency.
Private endpointTraffic to a specific provider service.Outbound-initiatedUsually the cheapest option, and it removes load from the NAT.
What each target commits you to.

How route changes actually break production

Routing incidents are rarely caused by a route being wrong in an obvious way. They are caused by a change that was correct in one table and applied to another, by a subnet quietly associated with the default table, by a more specific route added for a migration and never removed, or by a route deleted along with a resource that something else still depended on.

The pattern below is the one worth internalizing: a new, more specific prefix silently takes precedence over a working default route. Nothing errors at apply time. The change looks clean. Traffic to that prefix starts going somewhere that cannot deliver it, and the only symptom is that one class of outbound calls begins timing out while everything else works perfectly — which sends the investigation straight to the application.

The defence is that route tables belong in infrastructure code with a reviewed plan, and that flow logs are enabled so the reject or the missing return is visible as evidence rather than as a theory. See Infrastructure as Code and Reading a Plan Before You Apply It.

A "temporary" specific route that outlives its reason
# Added during a partner migration, on a Friday, by hand.
resource "route" "partner_via_test_appliance" {
  route_table_id         = rtb_private_a
  destination_cidr_block = "198.51.100.0/24"   # more specific than 0.0.0.0/0
  target                 = eni_test_appliance  # decommissioned three weeks later
}

# Symptom after the appliance is deleted:
#   calls to the partner hang for 30s and time out
#   every other outbound call is fine
#   application logs show a client timeout, so the investigation starts in the app
#   nothing in the route table looks alarming; the row is just there
Routes as reviewed code, with the reason attached
locals {
  private_routes = {
    default   = { cidr = "0.0.0.0/0",         target = nat_a,   why = "egress: package mirror, payment API" }
    partner   = { cidr = "198.51.100.0/24",   target = pcx_ptr, why = "partner peering, owner: payments team" }
    corporate = { cidr = "10.90.0.0/16",      target = tgw,     why = "corp access via transit hub" }
  }
}

# Every row has an owner and a reason. A plan diff shows the removal of a route
# as loudly as it shows the addition of one, and review catches a more-specific
# prefix pointed at something that is about to be deleted.

Longest prefix match means a new specific route silently overrides a working general one, and nothing errors. The only defence is that every row is reviewed, owned and removable as code rather than added by hand during an incident.

Key points

  • A route table maps destination prefixes to targets and is attached to subnets; the local route for the network's own range is always present and unremovable.
  • Longest prefix match decides, so a more specific route silently overrides a general one with no error at apply time.
  • Subnet boundaries are not security boundaries — the local route connects them all. Filtering is the job of security groups and ACLs.
  • Peering is non-transitive; a transit hub is the fix, and it costs per attachment and per gigabyte.
  • A routing failure always produces a hang, never a refusal — which is the fastest way to tell it apart from an application problem.

The loop, answered

Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.

How it works
  • Each subnet is associated with exactly one route table; unassociated subnets fall back to the network's main table.
  • For every outbound packet, the most specific matching destination prefix selects the target.
  • The local route covering the network range is inserted automatically and cannot be removed or overridden.
  • The target determines the path: internet gateway, NAT device, peering connection, transit attachment, private endpoint or a network interface.
  • A packet matching no route, or matching a route to a target that cannot deliver, is dropped silently.
What you still own
  • Own route tables in infrastructure code with a reason recorded per row, so a plan diff is a readable change.
  • Own the subnet-to-table associations explicitly; a subnet left on the main table is a recurring source of confusion.
  • Own removal as carefully as addition — a route pointing at a deleted target is a black hole, not an error.
  • Own route-table quotas in large environments; entries per table are limited and transit-hub propagation can fill them.
  • Own flow logs so a drop is evidence rather than a theory during an incident.
How it fails
  • Missing default route: all outbound calls hang and time out, with nothing logged on either side.
  • A route to a deleted target: traffic to that specific prefix black-holes while everything else works, so the investigation starts in the wrong place.
  • A subnet associated with the wrong table, giving a single tier different connectivity from its identical neighbours.
  • An assumption of transitive peering: A reaches B, B reaches C, and A→C fails with no error anywhere.
  • A route quota reached, so a legitimate change cannot be applied at exactly the moment it is needed.
How it scales
  • Entries per table and tables per network are quotas that bite in mature environments long before any bandwidth limit does.
  • A peering mesh needs a route in every table for every peer, which is what makes it grow quadratically and become unmanageable.
  • A transit hub converts that to one attachment and a propagated route set, at a fixed and per-gigabyte cost.
  • Route changes propagate in seconds, so routing is fast to change and correspondingly fast to break.
Security
  • The route table is the definitive exposure statement: whether a subnet has a path to an internet gateway is the single fact that makes it public.
  • Review route changes as security changes, because one row converts a private tier into a reachable one with no other signal.
  • Routing does not filter. It decides where a packet may go, not who may send it — that is the firewall's job. See Security Groups: The Stateful Firewall.
  • A default route to a NAT device is a working exfiltration path unless something else constrains outbound. See Private Connectivity.
Cost shape
  • Route tables and routes are free; the targets they point at are where the money is.
  • Choosing a NAT target over a private endpoint for provider traffic is a per-gigabyte decision made in a route table.
  • Peering and transit attachments bill per gigabyte and, for transit, per attachment-hour.
  • A single misdirected route can move a large traffic class onto a metered path without anything looking different.
What to watch
  • Flow logs showing rejects and, more importantly, flows with no return — the fingerprint of a black-holed route.
  • Configuration change events on route tables: who added or removed a row, and when, correlated against the incident start.
  • Per-target data volume, which is where a route mistake shows up on the bill before it shows up anywhere else.
  • The signal that lies: instance and application health checks. Neither traverses the route in question, so both stay green through a total loss of a destination.
Simpler alternatives
  • The default route table the provider created, for a single-subnet environment — a hand-built routing design buys nothing at that size.
  • A private endpoint instead of a route to a NAT device, whenever the destination is a provider service.
  • A managed platform that owns routing entirely, when there is no private tier and no partner network to reach.
  • Fewer networks: most peering and transit complexity exists because someone split networks per service instead of per environment. See Virtual Private Cloud.
What adopting this costs
  • Buys explicit, auditable control over every path; costs a change surface where a single row silently overrides a working design.
  • Buys cost control by choosing cheaper targets; costs the discipline to keep the table understandable as it grows.
  • A transit hub buys a manageable topology; costs a shared dependency whose failure affects every attached network.

What people believe, and what is true

Claim

Route order matters, so put the specific rules first.

Reality

Selection is by longest prefix match, not by order. A more specific route wins wherever it appears in the table.

Claim

Subnets isolate workloads from each other.

Reality

The local route connects every subnet in the network. Isolation between tiers comes from security groups and ACLs, never from subnet boundaries alone.

Claim

A wrong route produces an error.

Reality

It produces a drop. The client hangs until its TCP timeout, which is why routing problems are so often diagnosed as application slowness.

Apply it