Quick Answer

Quick Answer: A VPC is your own private, isolated slice of a cloud provider's network; subnetting is how you carve that slice into smaller address ranges pinned to specific Availability Zones. The practical skill is CIDR math — and the one thing most generic subnetting tutorials skip is that AWS reserves 5 addresses per subnet, not the usual 2.

What a VPC Actually Is

A Virtual Private Cloud is your own isolated network inside AWS's shared physical infrastructure. You choose the private IP address range it uses, you decide its route tables (what traffic goes where), you control which gateways exist (internet-facing, NAT, VPN, peering), and nothing in it is reachable from outside unless you explicitly route or expose it.

If you've ever set up a physical office network — a router, a switch, a defined IP range for everyone's laptop — a VPC is the same idea, except the "wiring" is virtual and defined entirely by configuration instead of cables. The upside is that the entire topology can be described in a few files and recreated identically a thousand times; the downside is that if you get the address planning wrong early, you often can't fix it without real disruption later, which is exactly why the CIDR math in this post matters.

CIDR Notation, Refreshed

CIDR notation looks like 10.0.0.0/16 — an IP address followed by a slash and a number called the prefix length. The prefix length is how many bits, counting from the left, are fixed as the "network" portion; everything after that is available for host addresses within that network.

Since an IPv4 address is 32 bits total, the number of addresses in a block is:

block size = 2^(32 - prefix)

/16  →  2^(32-16) = 2^16 = 65,536 addresses
/24  →  2^(32-24) = 2^8  = 256 addresses
/20  →  2^(32-20) = 2^12 = 4,096 addresses
/26  →  2^(32-26) = 2^6  = 64 addresses
/28  →  2^(32-28) = 2^4  = 16 addresses

Smaller prefix number = bigger block (more addresses). This trips up beginners constantly: a /16 is a much larger range than a /24, even though 16 < 24 — because fewer fixed bits leaves more bits free for hosts.

The AWS-Specific Gotcha: 5 Reserved Addresses, Not 2

On a traditional network, a subnet reserves exactly 2 addresses out of every block: the network address (all host bits zero) and the broadcast address (all host bits one). A /24 (256 addresses) therefore gives you 254 usable host addresses in classic networking.

In an AWS VPC, every subnet reserves 5 addresses, confirmed directly in AWS's own subnet-sizing documentation. For a subnet with CIDR block 10.0.0.0/24, the reserved ones are:

AddressReserved for
10.0.0.0Network address
10.0.0.1The VPC router
10.0.0.2DNS server (reserved by AWS)
10.0.0.3Reserved by AWS for future use
10.0.0.255Network broadcast address

So a /24 subnet in AWS gives you 251 usable addresses, not 254. This is a genuinely common planning mistake: someone sizes a subnet assuming the classic "minus 2" rule, plans for exactly 254 instances, and is quietly 3 addresses short when they try to launch the last few. AWS also documents the allowed subnet size range as between /28 (16 addresses, smallest) and /16 (65,536 addresses, largest) — and critically, a subnet's CIDR block cannot be changed after it's created. If you sized it wrong, the fix is creating a new subnet and migrating resources into it, not editing the existing one.

Worked Example: Splitting a /16 VPC Across 3 AZs and 2 Tiers

Here's a realistic design: a VPC sized 10.0.0.0/16, split into six subnets — a public and a private subnet in each of three Availability Zones. Public subnets will host load balancers and NAT gateways; private subnets will host application servers and databases with no direct route to the internet.

Splitting a /16 into /20 blocks gives six subnets of equal size (and leaves room to add more later — more on that below). This is the actual output of a short script that computes real network/broadcast addresses and usable ranges, not an estimate:

public-az1   10.0.0.0/20    network=10.0.0.0    broadcast=10.0.15.255   total=4096  aws_usable=4091  range=10.0.0.4-10.0.15.254
public-az2   10.0.16.0/20   network=10.0.16.0   broadcast=10.0.31.255   total=4096  aws_usable=4091  range=10.0.16.4-10.0.31.254
public-az3   10.0.32.0/20   network=10.0.32.0   broadcast=10.0.47.255   total=4096  aws_usable=4091  range=10.0.32.4-10.0.47.254
private-az1  10.0.48.0/20   network=10.0.48.0   broadcast=10.0.63.255   total=4096  aws_usable=4091  range=10.0.48.4-10.0.63.254
private-az2  10.0.64.0/20   network=10.0.64.0   broadcast=10.0.79.255   total=4096  aws_usable=4091  range=10.0.64.4-10.0.79.254
private-az3  10.0.80.0/20   network=10.0.80.0   broadcast=10.0.95.255   total=4096  aws_usable=4091  range=10.0.80.4-10.0.95.254

Each /20 gives 4,096 total addresses, minus the 5 AWS reserves, for 4,091 usable per subnet — comfortably enough for most application tiers. Add up the math: 6 subnets × 4,096 addresses = 24,576 addresses used, out of 65,536 total in the /16. That leaves 40,960 addresses (nearly two-thirds of the whole VPC) unused — deliberately. That headroom is what lets you add a fourth AZ, a new tier (say, a dedicated subnet for a managed database), or split an existing tier further, without having to re-plan the entire VPC's address space, which matters because a VPC's original CIDR block is also fixed once resources depend on it (AWS does allow adding a separate secondary CIDR block to an existing VPC, but the original primary range can't be resized).

A Smaller Worked Example: /24 Into Four /26 Subnets

The /16 example above is realistic for production, but the same math scales down to something closer to a home lab or small project. Take 192.168.10.0/24 and split it into four equal /26 subnets — again, real computed output:

subnet1 192.168.10.0/26   network=192.168.10.0   broadcast=192.168.10.63   total=64 aws_usable=59 range=192.168.10.4-192.168.10.62
subnet2 192.168.10.64/26  network=192.168.10.64  broadcast=192.168.10.127  total=64 aws_usable=59 range=192.168.10.68-192.168.10.126
subnet3 192.168.10.128/26 network=192.168.10.128 broadcast=192.168.10.191  total=64 aws_usable=59 range=192.168.10.132-192.168.10.190
subnet4 192.168.10.192/26 network=192.168.10.192 broadcast=192.168.10.255  total=64 aws_usable=59 range=192.168.10.196-192.168.10.254

Notice the pattern: each /26 block is exactly 64 addresses wide, so each subnet's network address jumps by 64 from the last (.0, .64, .128, .192) — that's the mechanical rule for splitting any block evenly. And again, 64 total minus 5 reserved leaves 59 usable, not the 62 you'd expect from classic "minus 2" networking math.

What Actually Makes a Subnet 'Public' or 'Private'

Here's a common misconception worth correcting directly: there is no setting, flag, or attribute on a subnet itself called "public" or "private." AWS doesn't store that label anywhere.

A subnet is "public" purely because its associated route table has a default route (0.0.0.0/0) pointing to an Internet Gateway. A subnet is "private" because its route table instead sends that default route to a NAT Gateway (for outbound-only internet access) or has no default route to the internet at all.

This matters practically: you could take any subnet from the worked example above and flip whether it's "public" or "private" just by changing what its route table points to — nothing about the CIDR block itself needs to change. The instances inside don't know or care; they just follow whatever route table is attached to their subnet.

Common Subnetting Mistakes Worth Avoiding

  • Forgetting the 5-reserved-IP rule when sizing. Planning for exactly the "classic" usable count and running 3 addresses short in production is a surprisingly common and entirely avoidable mistake.
  • Leaving no headroom for new AZs or tiers. If your six subnets consume the entire /16 with nothing left over, adding a fourth AZ later means re-planning the whole VPC instead of simply allocating the next unused /20.
  • Assuming a subnet's CIDR can be resized later. It cannot. The fix for an undersized subnet is always creating a new one and migrating resources — there's no in-place resize.
  • Mixing public and private resources in the same subnet. It technically works, but it makes route tables and security group reasoning harder — "everything in this subnet can reach the internet" should be a true, simple statement, not something you have to check resource-by-resource.
  • Sizing every subnet identically out of habit rather than by actual expected load — a private database subnet rarely needs the same address space as a public web tier, though keeping them equal is a fine, simple default when you're not sure yet.

If you want to practice this kind of networking math hands-on rather than just reading about it, the Priodemy courses catalog has backend and systems-design tracks that cover networking fundamentals like this in more depth.

Frequently Asked Questions

Can I resize a subnet after creating it?

No. Once a subnet's CIDR block is set, it cannot be changed. If you undersized it, the fix is creating a new subnet with a different CIDR and migrating resources into it.

Why does AWS reserve 5 IP addresses per subnet instead of the usual 2?

Three extra addresses go to the implicit VPC router, a reserved DNS server address, and one AWS holds for future use — confirmed directly in AWS's VPC subnet-sizing documentation.

What's the smallest and largest subnet AWS allows?

The smallest is /28 (16 total addresses, 11 usable after the 5 reserved); the largest is /16 (65,536 addresses). This range is documented in AWS's CreateSubnet API reference.

Is a 'public subnet' something I configure directly?

No — there's no public/private flag on a subnet itself. It's entirely a function of its route table: a default route to an Internet Gateway makes it effectively public; a route to a NAT Gateway (or no internet route) makes it private.

Can I expand a VPC's address space after creating it?

You can associate an additional secondary CIDR block to an existing VPC (supported since 2017), but the original primary CIDR block cannot be resized or shrunk — planning generously upfront still matters.