I have been writing backend code for years and I still cannot tell you, off the top of my head, how many usable hosts are in a /22. Every time I open an AWS VPC config or a Kubernetes cluster blueprint, I do the same Google search: "subnet calculator", click whatever ranks first, type my CIDR, write down the answer. Then I forget all of it within a week.
After the fourth or fifth time I had to look up whether /27 gives me 30 or 32 usable IPs, I sat down and built a one-page cheat sheet for myself. This post is that cheat sheet, plus the few mental shortcuts that have actually stuck. If you ship to cloud and only touch subnet math a few times a year, this is the post I wish I had bookmarked.
The three numbers that matter
For any CIDR, only three numbers matter in practice:
-
Block size — how many IPs total. Formula:
2^(32 − prefix). A/24has 256 addresses. -
Usable hosts — block size minus 2 (one for the network address, one for the broadcast). A
/24has 254 usable. -
Magic number —
256 − mask_octet. Tells you where the next subnet starts. For/26(mask255.255.255.192), the magic number is64, so subnets land at.0,.64,.128,.192.
Memorize one table and you will never need to do binary math in your head again:
| Prefix | Block size | Usable hosts | Mask | Magic |
|---|---|---|---|---|
| /22 | 1,024 | 1,022 | 255.255.252.0 | 4 (3rd octet) |
| /23 | 512 | 510 | 255.255.254.0 | 2 (3rd octet) |
| /24 | 256 | 254 | 255.255.255.0 | — |
| /25 | 128 | 126 | 255.255.255.128 | 128 |
| /26 | 64 | 62 | 255.255.255.192 | 64 |
| /27 | 32 | 30 | 255.255.255.224 | 32 |
| /28 | 16 | 14 | 255.255.255.240 | 16 |
| /29 | 8 | 6 | 255.255.255.248 | 8 |
| /30 | 4 | 2 | 255.255.255.252 | 4 |
That is the entire subnet math for 99% of cloud work. Print it, tape it to your monitor, move on.
The magic number trick, with a worked example
Suppose someone gives you the IP 10.20.30.45/26 and asks for the network and broadcast addresses. No calculator, no binary conversion.
- Prefix is
/26, so the magic number is64. - Look at the last octet:
45. Which multiple of 64 does it fall into?0,64,128,192.45is in the0–63block. - Network address:
10.20.30.0. Broadcast:10.20.30.63(one less than the next subnet, which would start at.64). - Usable range:
10.20.30.1to10.20.30.62.
Same trick for prefixes that fall in the third octet. 10.20.30.45/22 → magic number is 4, applied to the third octet. 30 falls in the 28–31 block (because 28 is the closest multiple of 4 at or below 30). Network: 10.20.28.0. Broadcast: 10.20.31.255. Block spans four /24s.
If your subnet boundaries do not look "round" — say 10.20.30.0/22 — that is a malformed CIDR. The network address must align to the magic number. 10.20.30.0 is not a valid /22 start; 10.20.28.0/22 is.
Cloud-specific gotchas you will hit
The textbook subnet math is the easy part. What actually trips up cloud engineers is the platform-specific quirks layered on top.
AWS VPC
AWS reserves five addresses per subnet, not two. The network address, the broadcast, and three more: the VPC router, DNS, and a future-use address. So a /28 AWS subnet has 11 usable IPs, not 14. This catches people every time they try to fit "exactly 14 EC2 instances" into a /28 and run out of IPs at instance #12.
VPC CIDR sizing also has hard limits: minimum /28, maximum /16. You can attach secondary CIDR blocks, but you cannot ever shrink a primary CIDR after creation. Always size up. A /16 gives you 65,536 addresses for free; there is no cost penalty for picking a large VPC CIDR and no benefit to picking a tight one.
Kubernetes
A typical EKS or GKE cluster needs three separate CIDRs:
- Node CIDR — a subnet of the VPC, one IP per node (plus AWS's five). Size for max-nodes × 2.
-
Pod CIDR — usually
10.244.0.0/16(Flannel default) or192.168.0.0/16(Calico). Each node gets a/24slice (Flannel) so the cluster maxes out at 254 nodes with a/16pod CIDR. Larger clusters need/12or smaller per-node allocations. -
Service CIDR — usually
10.96.0.0/12(kubeadm default). Sized for total Services, not Pods. A/16is overkill for most clusters; a/20is plenty.
The most common k8s networking mistake is overlapping the pod CIDR with the VPC CIDR. Pick non-overlapping ranges from day one — renumbering pod CIDR in a running cluster is a recreate-the-cluster operation.
Docker
Docker's default bridge is 172.17.0.0/16. The default docker network create allocates from 172.18.0.0/16 upward in /24 chunks. If your corporate VPN also uses 172.x.x.x ranges (very common), you will get phantom routing failures where Docker containers can reach the internet but cannot reach internal services. Reconfigure Docker's default address pools in /etc/docker/daemon.json:
{
"default-address-pools": [
{"base": "10.99.0.0/16", "size": 24}
]
}
This carves Docker out of the 172.x space and into a private 10.x range your VPN almost certainly does not use.
Subnet math in code (Python and Go)
When you actually need to compute subnets programmatically — generating Terraform, validating user input, planning a migration — every modern language has a stdlib for this.
Python uses ipaddress:
import ipaddress
# Parse a network
net = ipaddress.ip_network("10.20.30.0/22")
print(net.network_address) # 10.20.28.0 — auto-normalized!
print(net.broadcast_address) # 10.20.31.255
print(net.num_addresses) # 1024
print(len(list(net.hosts()))) # 1022 — excludes network + broadcast
# Check if an IP is in a subnet
ip = ipaddress.ip_address("10.20.29.50")
print(ip in net) # True
# Subdivide a /22 into /24s
for sub in net.subnets(new_prefix=24):
print(sub)
# 10.20.28.0/24
# 10.20.29.0/24
# 10.20.30.0/24
# 10.20.31.0/24
# Detect overlap (catches the VLSM mistake from earlier)
a = ipaddress.ip_network("10.0.0.0/25")
b = ipaddress.ip_network("10.0.0.64/26")
print(a.overlaps(b)) # True
Note: passing strict=False to ip_network lets you parse 10.20.30.0/22 (a malformed CIDR — .30 is not the network address). With strict=True (the default since Python 3.9) it raises ValueError. Use strict=True in production input validation; the explicit error is far more useful than a silently-corrected network.
Go uses net/netip (Go 1.18+):
package main
import (
"fmt"
"net/netip"
)
func main() {
prefix, _ := netip.ParsePrefix("10.20.30.0/22")
// Normalize to actual network address
prefix = prefix.Masked()
fmt.Println(prefix.Addr()) // 10.20.28.0
fmt.Println(prefix.Bits()) // 22
ip, _ := netip.ParseAddr("10.20.29.50")
fmt.Println(prefix.Contains(ip)) // true
}
The older net.IPNet API still works but net/netip is value-typed, allocation-free, and the recommended choice for new code.
A 30-second AWS CLI sanity check
When debugging "why can't my EC2 instance reach this other EC2 instance," step one is always: are they on subnets that route to each other? Quick CLI check:
# List subnets with their CIDRs in the current VPC
aws ec2 describe-subnets \
--query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone]' \
--output table
# Check route table for a subnet
aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-abc123 \
--query 'RouteTables[*].Routes' \
--output table
If two subnets are in the same VPC and the route tables both have local routes for the VPC CIDR, they can reach each other (assuming security groups and NACLs allow). Most "why no connectivity" tickets resolve here.
Three mistakes I have personally shipped to production
Subnet too small. A /28 for "we only need 10 instances" — then AWS takes 5, leaving 11. Add a load balancer, add scaling, you are out of IPs. Always start at /24 for any production subnet unless you have a hard reason not to.
Pod CIDR overlapping VPC CIDR. Created a GKE cluster with default pod CIDR 10.0.0.0/14 inside a VPC at 10.0.0.0/16. Cluster came up, pods on the same node could reach each other, pods on different nodes silently dropped traffic. Three hours debugging later: pick non-overlapping CIDRs.
Forgot AWS's 5 reserved IPs. Provisioned 14 EC2 instances in a fresh /28. First 11 launched fine, the rest failed with InsufficientFreeAddressesInSubnet. The fix is /27 (32 addresses, 27 usable after AWS reservations).
Closing — make subnet math boring
The whole point of the cheat sheet is that subnet math should be boring. You should not have to think about it. You should be able to glance at a CIDR, know the block size and the magic number, and move on to whatever interesting problem you were actually trying to solve.
If you need to verify a non-trivial CIDR or plan a VLSM allocation across multiple subnets, I built a free subnet calculator that handles IPv4, IPv6, and VLSM planning. There's also a longer-form guide on the math behind CIDR, VLSM, and IPv6 subnetting if you want to go deeper than this cheat sheet.
The tool is free, no signup, no tracking. If it saves you a Google search next quarter, that is the entire goal.
Cross-posted with canonical from calculators.im.
Top comments (1)
Fantastic breakdown of the "magic number" trick to bypass manual binary conversions when planning architectures. Highlighting cloud-specific realities like AWS's 5 reserved IPs is a crucial warning that saves engineers from major provisioning headaches!