camzabriskie.com

Primers › Networking

AWS VPCs, subnets, and routing

03 / 10 updated Oct 5, 2026
On this page
  1. The VPC and its subnets
  2. The five reserved addresses
  3. Route tables
    1. Source picks the table, destination picks the row
    2. Destination and target
    3. When more than one route matches
    4. Following a packet
    5. Every subnet has its own
    6. What the targets are
    7. Public and private subnets
  4. Security groups and network ACLs
    1. Which one to use
    2. When traffic doesn’t get through
  5. Planning a layout
  6. Don’t overlap with networks you’ll connect to
  7. References

Most of the subnets I deal with live in AWS, inside a VPC (Virtual Private Cloud), which is your own private network in an AWS region. AWS follows the same CIDR rules as everywhere else and adds a few of its own on top. Inside the VPC, route tables decide where traffic can go and two kinds of firewall decide what’s allowed to get there.

This builds on IP addresses and CIDR, so if /20 or “starts on a multiple of 16” doesn’t mean anything yet, start there.

The VPC and its subnets

The five reserved addresses

AWS takes five addresses out of every subnet, the first four and the last one, instead of the usual two (network and broadcast). In 10.0.1.0/24:

AddressReserved for
10.0.1.0the network address
10.0.1.1the VPC router, which is the subnet’s gateway to everything else
10.0.1.2DNS (the DNS server itself sits at the VPC’s base address plus two, so 10.0.0.2 in a 10.0.0.0/16 VPC; see where Route 53 fits)
10.0.1.3reserved by AWS for future use
10.0.1.255the broadcast address. VPCs don’t support broadcast, but AWS reserves it anyway.

That leaves 2(32 − n) − 5 usable addresses [2]: 251 in a /24, 59 in a /26, and only 11 in a /28, the smallest subnet AWS allows. The first address you can hand out is always the fifth one (10.0.1.4 here).

Route tables

Every subnet is associated with one route table [6]. The packet’s source IP picks the route table, and its destination IP picks the row in it. Whenever something in a subnet sends a packet, the VPC uses that subnet’s route table, looks up the packet’s destination address in it, and the matching row says where to send it next. A route table doesn’t allow or block anything (that’s the firewalls’ job, further down). It only picks the next step.

Source picks the table, destination picks the row

Every packet carries two addresses: its source, where it came from, and its destination, where it’s going. When an instance at 10.0.1.25 sends something to a server at 10.20.5.9, the source is 10.0.1.25 and the destination is 10.20.5.9, and both stay written on the packet the whole way.

The route table uses them for two different jobs:

  1. The source IP picks the route table. 10.0.1.25 belongs to one subnet, so the packet uses that subnet’s route table. (Strictly, AWS uses the route table of the subnet the packet leaves from. For an instance sending its own traffic, that’s the subnet its source IP is in, so it comes to the same thing.) The source address never appears inside the table. It’s already settled by which table you’re looking at.
  2. The destination IP picks the row. The VPC compares 10.20.5.9 against each row’s Destination column and uses the row that matches (the most specific one, if several do).

So a route table never asks “where did this come from?” It only asks “where is this going, and which way out gets it closer?”

Destination and target

Each route is one row with two columns, and AWS’s names for them are confusing, because in everyday English “destination” and “target” mean the same thing. In a route table they don’t:

Driving directions work the same way. If you’re going to Salt Lake City, that’s your destination, and the sign that says “Salt Lake City: take I-15 North” doesn’t take you there. It tells you which road to get on next. When you reach the next junction, another sign tells you the next road. A route table is a set of those signs for one subnet: for addresses in this range, take this way out. The packet keeps its real destination the whole time, and each place it passes through looks it up in its own route table to pick the next hop. A peering connection hands the packet to the other VPC, and that VPC’s route tables take it from there.

Here’s a route table for a subnet in a VPC that uses 10.0.0.0/16, is peered with another VPC that uses 10.20.0.0/16, and has an internet gateway:

Destination (where it’s headed)Target (next hop)Read it as
10.0.0.0/16localHeaded anywhere in this VPC? Deliver it inside the VPC.
10.20.0.0/16pcx-… (a peering connection)Headed for the other VPC’s range? Hand it to the peering connection.
0.0.0.0/0igw-… (an internet gateway)Headed anywhere else? Hand it to the internet gateway.

0.0.0.0/0 matches every IPv4 address there is, because a /0 locks none of the bits. It’s called the default route, the place traffic goes when nothing more specific applies.

When more than one route matches

Since 0.0.0.0/0 matches everything, almost every packet matches at least two routes: the default route and something more specific. The route table needs a rule for picking one, and the rule is that the route with the longest prefix wins, meaning the biggest number after the slash. AWS calls this longest prefix match [7].

A bigger number after the slash means a smaller, more specific range. /0 is every address, /16 is 65,536 of them, /24 is 256, and /32 is exactly one. So the rule amounts to this: the route that describes the destination most precisely wins.

Sorting mail works the same way. Say there’s one bin for anything going to the US, one for anything going to Utah, and one for anything going to Salt Lake City. A letter for Salt Lake City fits in all three bins, but it goes in the Salt Lake City one, because that’s the most specific. A letter for Denver only fits the US bin, so that’s where it goes. Routes are the same, with CIDR ranges in place of places, and 0.0.0.0/0 as the “anywhere” bin.

Here’s the table above deciding where a packet headed for 10.20.5.9 goes:

RouteIs 10.20.5.9 in its Destination range?Prefix length
10.0.0.0/16 → localNo. This range only covers 10.0.x.x.
10.20.0.0/16 → pcx-…Yes16
0.0.0.0/0 → igw-…Yes, every address does0

Two routes match, and 16 is longer than 0, so the packet goes over the peering connection.

The order of the rows doesn’t matter, only how specific each route is. (Network ACLs work the other way, checking rules in number order, which is one of the reasons the two get mixed up.) This makes it easy to have a broad default and carve out exceptions. If the table also had 10.20.8.0/24 → tgw-…, then 10.20.8.7 would go to the transit gateway (/24 beats /16), while 10.20.5.9 would still go over the peering connection, because it isn’t in 10.20.8.0/24.

Following a packet

Here are three packets leaving an instance at 10.0.1.25 (the source, which is why this subnet’s route table is the one used), each headed for a different destination:

Packet going toRoutes it matchesWinnerWhat happens
10.0.2.40, a database in another subnet of the same VPC10.0.0.0/16 and 0.0.0.0/0local (/16)Delivered directly inside the VPC
10.20.5.9, a server in the peered VPC10.20.0.0/16 and 0.0.0.0/0pcx-… (/16)Sent over the peering connection
203.0.113.50, a server on the internetonly 0.0.0.0/0igw-… (/0)Sent out through the internet gateway

Try any destination address against the same table:

(This version includes the 10.20.8.0/24 route from above, so 10.20.8.7 and 10.20.5.9 end up going different ways.)

Every subnet has its own

What the targets are

These are the next hops a route can point at. Each one is a way out of the subnet, and the ID prefix tells you which kind it is when you’re reading a route table in the console.

TargetLooks likeSends traffic to
LocallocalOther addresses in the same VPC
Internet gatewayigw-…The public internet (the resource also needs a public IP)
NAT gatewaynat-…The internet, outbound only (see below)
Peering connectionpcx-…One other VPC
Transit gatewaytgw-…A hub connecting many VPCs and VPNs
Virtual private gatewayvgw-…A site-to-site VPN to an office or data center
Gateway VPC endpointvpce-…S3 or DynamoDB, without going out to the internet

AWS’s routing options page shows an example route for each [8].

Public and private subnets

AWS has no “public” setting on a subnet. A subnet is public or private because of its route table [3]:

NAT gateways come in two kinds. The standard kind, which AWS now calls a zonal NAT gateway, lives in one availability zone and sits in a public subnet, so the usual setup is one per zone, with each zone’s private subnets routing to the NAT gateway in the same zone. Then losing a zone only takes out that zone’s internet access [10]. A regional NAT gateway is newer: one NAT gateway that spreads across availability zones on its own as your workloads appear in them, and doesn’t need a public subnet at all [11]. Either way, NAT gateways charge for the data they process, which is why traffic to S3 usually gets its own more specific route to a gateway VPC endpoint instead of going through the NAT [8].

Security groups and network ACLs

A VPC has two layers of firewall, and they behave differently enough that mixing them up causes a lot of confusion.

Security groups attach to a resource’s network interface: an EC2 instance, a load balancer, a database, a Lambda function running in the VPC.

Network ACLs attach to a subnet, and every subnet has exactly one. NACL is short for network access control list (people usually say it like “nackle”). An access control list, or ACL, is just what it sounds like: a list of rules, each one saying a kind of traffic (a protocol, a port range, and an address range) is allowed or denied, checked in order from the top. The term comes from older routers and firewalls, and the “network” in front distinguishes AWS’s subnet-level version from other ACLs in AWS, like the ones on S3 buckets.

Because NACLs are stateless, the reply side catches people out. A reply goes back to whatever port the client picked for its end of the connection, called an ephemeral port, and different clients pick from different ranges. Linux usually uses 32768–61000, newer Windows uses 49152–65535, and NAT gateways, load balancers, and Lambda use 1024–65535. So a web server’s subnet needs an inbound NACL rule for port 443 and an outbound rule for 1024–65535 (AWS’s suggested catch-all) to let the replies out [14].

Security groupNetwork ACL
Attached toA resource’s network interfaceA subnet
StateStateful: replies allowed automaticallyStateless: replies need their own rules
RulesAllow onlyAllow and deny, checked in number order
DefaultNew groups: nothing in, everything outDefault NACL: everything allowed. New NACLs: everything denied.
Typical useThe main firewall for each resourceA coarse fence around a whole subnet

Traffic coming into a subnet passes the NACL first and then the resource’s security group, and it has to get through both [17]. Neither one filters traffic to the VPC’s own DNS server (the base address plus two) or the instance metadata service, so you can’t block those with either [13], [12].

Which one to use

Security groups, nearly always. AWS says so directly: “in most cases, security groups can meet your needs,” and NACLs are there “if you want an additional layer of security” [3]. AWS’s comparison of the two says the same: you can secure instances with security groups alone and add NACLs as an extra layer [17]. Plenty of VPCs leave the default NACL, which allows everything, alone for good. In practice, NACLs get used for a few specific jobs a security group can’t do:

  1. Denying something. Security groups can only allow. If one address range is causing trouble and everything else should still get in, that takes a NACL deny rule. (For web traffic, a WAF is usually a better place to block, since it can see the requests.)
  2. A rule for a whole subnet that no security group can override. “The database subnets only accept traffic from the app subnets’ range” is a good NACL. Even if someone attaches a wide-open security group to a database by mistake, the NACL still blocks everything else. It’s a backstop, and it’s often owned by a different team than the security groups are.
  3. A requirement to separate subnets at the network level, which some compliance frameworks ask for.

NACLs make a poor main firewall. They’re stateless, so you manage reply ports yourself. They only understand address ranges, so you can’t say “from the app servers’ security group.” They allow only 20 inbound and 20 outbound rules by default, and 40 each at most, compared with 60 each per security group [5]. And they apply to everything in the subnet at once, so one mistake breaks every resource in it.

An apartment building is a decent comparison. The security group is the lock on each apartment’s door, set up for whoever lives there. The NACL is the guard at the building’s front gate with one list for everybody, which is useful for keeping certain people out of the whole building and no good for deciding who gets into apartment 4B.

SituationUse
App servers need to reach the database on port 5432Security group on the database, allowing the app servers’ security group
People on the VPN need SSH to some instancesSecurity group, allowing the VPN’s range on port 22
One address range keeps hammering a public serviceNACL deny rule on that subnet (or a WAF rule, for web traffic)
Database subnets should only ever hear from the app subnetsNACL on the database subnets, as a backstop under the security groups
Database subnets should never reach the internetThe route table: no route to an internet gateway or NAT gateway. If there’s no way out, no firewall rule is needed.

When traffic doesn’t get through

Most “can’t connect” problems in a VPC come down to one of these, and the return path is the one that gets missed:

  1. A route there. The source’s subnet has a route that covers the destination.
  2. A route back. The destination’s subnet has a route back to the source. For anything coming from outside the VPC (a VPN, a peered VPC), this is a separate route you have to add, and without it the request arrives but the reply has nowhere to go.
  3. The NACLs, both ways. On both subnets, including the outbound rule for ephemeral ports.
  4. The security group on the destination. It allows the source’s range (or security group) on the right port.
  5. Something is actually listening. The service is running and listening on that address and port, not only on localhost.

Planning a layout

A common pattern is a /16 VPC with a public subnet and a private subnet in each of three availability zones. Public subnets hold the few things that face the internet, mostly load balancers and zonal NAT gateways, so they can stay small. Private subnets hold everything else and should be much bigger:

SubnetCIDRUsable
public, zone a10.0.0.0/24251
public, zone b10.0.1.0/24251
public, zone c10.0.2.0/24251
private, zone a10.0.16.0/204,091
private, zone b10.0.32.0/204,091
private, zone c10.0.48.0/204,091

Each /20 starts on a multiple of 16 in the third octet, which is the boundary rule from the CIDR primer. 10.0.3.0 through 10.0.15.255 and everything from 10.0.64.0 up are left empty on purpose, so new subnets have room later without any renumbering.

Private subnets need room because more things use VPC addresses than you’d expect. Every EC2 instance, load balancer node, database, and Lambda network interface takes at least one. On EKS with the default networking setup, every Kubernetes pod takes its own VPC address too [18], so a cluster can run through a /24 surprisingly quickly.

Don’t overlap with networks you’ll connect to

If a VPC will ever be connected to another VPC (peering, a Transit Gateway) or to an office network over a VPN, their ranges can’t overlap. A route table has no way to tell which 10.0.1.0/24 you mean, and VPC peering refuses overlapping ranges outright [19]. Giving every VPC 10.0.0.0/16 works fine until the day two of them need to talk, so give each VPC its own range from the start, like 10.0.0.0/16, 10.1.0.0/16, 10.2.0.0/16, and so on.

(The default VPC AWS creates in every region uses 172.31.0.0/16, with a /20 subnet in each availability zone [20]. It’s handy for experiments. Leave 172.31.0.0/16 out of the VPCs you plan yourself, so they never overlap with a default VPC you might want to connect to later.)

References

Show all 20 sourcesHide sources
  1. [1] ↩ a b Amazon Web Services, “VPC CIDR blocks,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html
  2. [2] ↩ a b Amazon Web Services, “Subnet CIDR blocks,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html
  3. [3] ↩ a b c d Amazon Web Services, “Subnets for your VPC,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html
  4. [4] ↩ Amazon Web Services, “CreateSubnet,” Amazon EC2 API Reference. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSubnet.html
  5. [5] ↩ a b Amazon Web Services, “Amazon VPC quotas,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/amazon-vpc-limits.html
  6. [6] ↩ Amazon Web Services, “Subnet route tables,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/subnet-route-tables.html
  7. [7] ↩ Amazon Web Services, “How route priority works,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/route-tables-priority.html
  8. [8] ↩ a b Amazon Web Services, “Example routing options,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/route-table-options.html
  9. [9] ↩ Amazon Web Services, “NAT gateways,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html
  10. [10] ↩ Amazon Web Services, “NAT gateway basics,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-basics.html
  11. [11] ↩ Amazon Web Services, “Regional NAT gateways for automatic multi-AZ expansion,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateways-regional.html
  12. [12] ↩ a b Amazon Web Services, “Control traffic to your AWS resources using security groups,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html
  13. [13] ↩ a b Amazon Web Services, “Control subnet traffic with network access control lists,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html
  14. [14] ↩ a b Amazon Web Services, “Custom network ACLs for your VPC,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/custom-network-acl.html
  15. [15] ↩ Amazon Web Services, “Default network ACL for a VPC,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/default-network-acl.html
  16. [16] ↩ Amazon Web Services, “Create a network ACL for your VPC,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/create-network-acl.html
  17. [17] ↩ a b Amazon Web Services, “Infrastructure security in Amazon VPC,” Amazon VPC User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/userguide/infrastructure-security.html
  18. [18] ↩ Amazon Web Services, “Assign IPs to Pods with the Amazon VPC CNI,” Amazon EKS User Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/eks/latest/userguide/managing-vpc-cni.html
  19. [19] ↩ Amazon Web Services, “How VPC peering connections work,” Amazon VPC Peering Guide. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.aws.amazon.com/vpc/latest/peering/vpc-peering-basics.html
  20. [20] ↩ Amazon Web Services, “Amazon VPC FAQs.” Accessed: Oct. 5, 2026. [Online]. Available: https://aws.amazon.com/vpc/faqs/

Every source cited across the primers is collected on one page.