AWS Certified CloudOps Engineer - Associate
VPC, Subnets, and Route Tables
A VPC is an address range plus a router, and almost every networking incident traces back to one of those two. This lesson covers CIDR planning that you cannot undo, why a subnet gives you fewer addresses than the math suggests, and how route tables decide where every packet goes.
- Explain how a VPC CIDR block, subnets, and Availability Zones fit together, and which of those choices you cannot reverse later
- Calculate the usable IP addresses in a subnet by accounting for the 5 addresses AWS reserves
- Identify what makes a subnet public or private, and why it is never a subnet setting
- Distinguish the main route table from a custom route table and predict which one an unassociated subnet uses
- Apply longest prefix match and the static-versus-propagated rules to resolve overlapping routes
- Size subnets for a multi-AZ workload without exhausting the VPC CIDR
At 03:00 an Auto Scaling group stops launching. The event log says There are not enough free addresses in subnet subnet-0a1b2c3d to satisfy the requested number of instances. The subnet is 10.0.4.0/27, the team sized it for 32 instances, and only 27 ever launched. Nothing is broken. The subnet did exactly what AWS documents it will do, and nobody read that part before picking the mask.
Address planning is the part of VPC design you cannot walk back. You can add a security group rule in 10 seconds and delete it in 10 more. You cannot resize a CIDR block at all. So this lesson starts with the address math, then moves to the route table, which is the other half of every VPC and the first place to look when traffic goes somewhere unexpected.
A VPC is an address range plus a router
Strip away the console and a VPC is 2 things: a block of IP addresses you claimed, and an implicit router that AWS runs for you inside that block. Everything else in this domain, subnets, gateways, endpoints, peering, is a way of telling that router where to send packets it does not already know about.
When you create a VPC you must give it an IPv4 CIDR block, and the allowed size runs from a /16 (65,536 addresses) down to a /28 (16 addresses). AWS recommends a block from the private ranges in RFC 1918:
| RFC 1918 range | Example VPC CIDR |
|---|---|
| 10.0.0.0 to 10.255.255.255 | 10.0.0.0/16 |
| 172.16.0.0 to 172.31.255.255 | 172.31.0.0/16 |
| 192.168.0.0 to 192.168.255.255 | 192.168.0.0/20 |
Four blocks are refused outright: 0.0.0.0/8, 127.0.0.0/8 (loopback), 169.254.0.0/16 (link-local), and 224.0.0.0/4 (multicast). One more is allowed but should be avoided: several AWS services, including AWS Cloud9 and SageMaker AI, use 172.17.0.0/16 internally, and picking it invites conflicts inside those environments that are painful to diagnose.
You can also run a VPC on publicly routable addresses that you own. AWS still refuses to route from your VPC CIDR straight to the internet, and it never advertises a subnet range to the internet, so you always go through a gateway regardless.
The CIDR decisions you cannot undo
Three rules make the first CIDR choice expensive to get wrong.
You cannot resize a CIDR block. Not up, not down. If 10.0.0.0/16 runs out, you do not turn it into a /15.
You cannot remove the primary CIDR. You can associate secondary IPv4 blocks (5 per VPC by default, adjustable up to 50) and disassociate those later. The block you created the VPC with stays for the life of the VPC.
Secondary blocks are restricted by the range the primary sits in. If any CIDR on the VPC comes from 10.0.0.0/8, AWS refuses to add a block from 172.16.0.0/12 or 192.168.0.0/16. The same exclusion applies in each direction. AWS applies these restrictions because cross-VPC and cross-account features on the AWS side need non-conflicting blocks. Two more traps sit in the same rule set: if any associated block falls in 10.0.0.0/15, you cannot add one from 10.0.0.0/16, and a new secondary block must not be the same size as or larger than any destination CIDR already sitting in one of your route tables.
Adding a secondary CIDR does one thing automatically: a new local route appears in every route table for that VPC, with the new block as the destination.
For IPv6 the shape is different. You request a block from Amazon's pool and you do not choose the range; a typical allocation looks like 2001:db8:1234:1a00::/56. You can associate up to 5 IPv6 blocks per VPC, from /44 to /60 in increments of /4. Every Amazon-provided IPv6 address is globally unique and therefore public by default, which becomes the whole story in the next lesson.
A subnet lives in exactly one Availability Zone
A subnet is a slice of the VPC range, and it resides entirely within one Availability Zone. It cannot span zones. That single sentence sets the shape of every VPC you will ever build: to run a 2-tier workload across 3 zones you need 6 subnets, not 2.
Subnet IPv4 blocks run from /28 to /16, they must sit inside the VPC block, and they cannot overlap each other. The default quota is 200 subnets per VPC. For IPv6, subnet netmask lengths run from /44 to /64 in increments of /4, and a /64 is the conventional choice.
The subnet types you will see named in exam questions are defined purely by routing, not by a setting:
| Subnet type | What its route table has |
|---|---|
| Public | A route to an internet gateway |
| Private | No route to an internet gateway (usually a route to a NAT device instead) |
| VPN-only | A route to a Site-to-Site VPN connection through a virtual private gateway, and no internet gateway route |
| Isolated | No routes to anything outside the VPC |
One real subnet setting does exist and is worth knowing because it is often confused with the public/private distinction: auto-assign IP settings, which decides whether a network interface created in that subnet automatically receives a public IPv4 address, and an IPv6 address if applicable. You can override it per instance at launch. It controls whether the instance has a public address, not whether traffic can reach the internet. Both are required, and they are configured in 2 different places.
Where 5 addresses go in every subnet
Back to the 03:00 page. In a subnet with CIDR 10.0.1.0/28, AWS reserves the first 4 addresses and the last 1:
| Address | Reserved for |
|---|---|
10.0.1.0 | Network address |
10.0.1.1 | The VPC router |
10.0.1.2 | The Amazon DNS server |
10.0.1.3 | Future use by AWS |
10.0.1.15 | Network broadcast address (broadcast is not supported in a VPC) |
Sixteen addresses in, 11 out. The overhead is a flat 5 at every size, so it hurts small subnets badly and large ones barely at all:
| Subnet CIDR | Total addresses | Usable |
|---|---|---|
/28 | 16 | 11 |
/27 | 32 | 27 |
/24 | 256 | 251 |
/20 | 4,096 | 4,091 |
This is where the predictable misconception lives. On premises you learn that a subnet loses 2 addresses, the network address and the broadcast address, and that rule is correct nearly everywhere except AWS. AWS takes 3 more. If you size a subnet by subtracting 2, you will be short by 3 exactly when it matters, during a scaling event.
A note on 10.0.1.2. The Route 53 Resolver that your instances actually query lives at the base of the VPC range plus 2, and for a VPC with several CIDR blocks it sits in the primary block. AWS still reserves base-plus-2 in every subnet of every block, which is why the address is unavailable in each subnet even though only one of them hosts the resolver.
Two more consumers of subnet addresses surprise people during capacity planning: a NAT gateway takes a private address from the subnet it sits in, and every interface VPC endpoint takes one per subnet it is enabled in. Neither shows up as an instance.
Sizing a real VPC
Take a workload with a web tier and a database tier across 2 Availability Zones, expected to grow.
VPC 10.0.0.0/16 65,536 addresses
Public AZ-a 10.0.0.0/24 251 usable (ALB nodes, NAT gateway)
Public AZ-b 10.0.1.0/24 251 usable
Private AZ-a 10.0.16.0/20 4,091 usable (application instances)
Private AZ-b 10.0.32.0/20 4,091 usable
Data AZ-a 10.0.48.0/24 251 usable (RDS subnet group)
Data AZ-b 10.0.49.0/24 251 usable
Two things about this layout are deliberate. Public subnets are small because load balancer nodes and a NAT gateway need very few addresses, and private subnets are generous because that is where scaling happens. The blocks are also spaced out rather than packed end to end, leaving room to carve 10.0.64.0/20 and beyond for a third zone or a new tier without renumbering anything.
Verify capacity from the CLI rather than from a spreadsheet:
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-0abc123def4567890" \
--query "Subnets[].{Subnet:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock,Free:AvailableIpAddressCount}" \
--output table
AvailableIpAddressCount is the number that matters during an incident. It already accounts for the 5 reserved addresses and for everything currently deployed.
Route tables decide where packets go
Every subnet must be associated with a route table. Associate one explicitly, or the subnet is implicitly associated with the VPC main route table, which AWS creates with the VPC.
Each route has a destination (a CIDR block or a prefix list) and a target (an internet gateway, NAT gateway, network interface, peering connection, transit gateway, and so on). The router compares a packet's destination address against the destinations in the table and hands the packet to the matching target.
Every route table also contains a local route, one per associated CIDR block, IPv4 and IPv6 counted separately. It covers traffic inside the VPC, it is added automatically, and you cannot delete it. You can replace or restore its target, and you can add a route more specific than the local route as long as the destination matches an entire subnet CIDR and the target is a NAT gateway, a network interface, or a Gateway Load Balancer endpoint. That exception exists so you can force traffic between 2 subnets through an inspection appliance.
The main route table has its own rules. You can edit its routes but not delete it, you cannot make a gateway route table the main table, and you can replace it by making a custom table the main one. AWS recommends leaving it with only the local route and associating every subnet explicitly, and there is a sharp reason for that recommendation. Put a 0.0.0.0/0 route to an internet gateway in the main route table and every subnet you create from then on, until someone associates it elsewhere, is a public subnet. Nobody sets out to do that. It happens when a route is added to the table that happened to be open in the console.
Quotas worth remembering: 200 route tables per VPC, 500 non-propagated routes per route table (adjustable to 1,000), and 100 propagated routes, which is not adjustable.
Two routes match. Now what?
Route tables are not firewall rule lists. They are not evaluated top to bottom, and there is no rule order to reason about. AWS picks the most specific matching route, which is the longest prefix match.
Walk one table:
| Destination | Target |
|---|---|
10.0.0.0/16 | local |
172.31.0.0/16 | pcx-11223344556677889 |
0.0.0.0/0 | igw-12345678901234567 |
A packet for 172.31.5.10 matches both 172.31.0.0/16 and 0.0.0.0/0. The /16 is more specific, so the packet goes to the peering connection. A packet for 93.184.216.34 matches only 0.0.0.0/0 and goes to the internet gateway. A packet for 10.0.4.19 matches the local route and never leaves the VPC.
When 2 routes have the same destination, the tie is broken by a priority ladder:
- Longest prefix (this settles most cases before the rest matter)
- Static routes
- Prefix list routes
- Propagated routes, in the order Direct Connect BGP routes, then VPN static routes, then VPN BGP routes
Static routes are the ones you create, plus those created by an internet gateway, NAT gateway, network interface, instance ID, gateway VPC endpoint, transit gateway, VPC peering connection, or Gateway Load Balancer endpoint. Propagated routes are the ones that appear automatically when you attach a virtual private gateway and enable route propagation.
This ladder produces one of the quieter production failures in AWS networking. A hybrid VPC propagates 172.31.0.0/24 from the on-premises network through a virtual private gateway. Someone later adds a static route for the same 172.31.0.0/24 to an internet gateway. Nothing errors. The static route wins, and traffic that should have crossed the VPN heads for the public internet instead. If a scenario says traffic bound for a data center is "leaving through the internet gateway", check for a static route that shadows the propagated one.
Two ranges cannot be routed at all: 169.254.168.0/22 for IPv4 and fd00:ec2::/32 for IPv6. AWS reserves them for services reachable only from instances, such as the Instance Metadata Service and the Amazon DNS server. A larger overlapping block is accepted, but packets aimed inside the reserved range are not forwarded.
Exam tips
- Subnet address math: subtract 5, never 2. A
/24gives 251 usable addresses, a/28gives 11. Insufficient-address errors during scaling are this calculation. - Public versus private is a property of the route table, never of the subnet. A question that says "the subnet was configured as private" is describing a route table, and the fix is always routing.
- Reaching the internet takes both a route to an internet gateway and a public IPv4 or IPv6 address on the resource. Missing either one produces the same symptom.
- A subnet with no explicit route table association uses the main route table. This is how subnets accidentally become public.
- Longest prefix match settles overlapping routes. Route tables have no evaluation order, so "the first matching route" is always a wrong answer.
- Identical destinations: static beats propagated. Watch for this in hybrid scenarios where traffic is going out the wrong gateway.
- You cannot resize a CIDR block or remove the primary CIDR. If an option offers to expand a VPC CIDR in place, it is wrong.
- Secondary CIDR restriction: a VPC using one RFC 1918 range cannot add a block from a different RFC 1918 range.
- A subnet sits in one Availability Zone. Multi-AZ means more subnets, not bigger ones.
- The local route cannot be deleted. It can have its target replaced, and it can be overridden by a more specific route whose destination is an entire subnet CIDR and whose target is a NAT gateway, network interface, or Gateway Load Balancer endpoint.
The habit to carry forward is that a VPC answers exactly 2 questions about any packet: does this address belong to me, and if not, which target do I hand it to? Address planning answers the first, route tables answer the second, and almost every connectivity ticket in this domain is one of those 2 answers being wrong. The next lesson takes the most common target you will put in a route table, the gateways that connect a VPC to the internet, and shows why 3 of them exist instead of one.
