❌ Starting Too Small
Using /16 as base CIDR when /8 would allow future growth without re-IP.
This guide covers proven strategies for designing your cloud network architecture with Subnetter.
Always start with the largest CIDR block you can reasonably use. It’s much easier to leave space unused than to expand later.
| Base CIDR | Total IPs | Recommended For |
|---|---|---|
/8 | 16.7M | Large enterprise, multi-cloud |
/12 | 1M | Medium enterprise, single cloud |
/16 | 65K | Small organization, single region |
Use larger prefix lengths than strictly necessary to leave room for growth:
Recommended Hierarchy:├── Base: /8 (16.7M addresses)├── Account: /16 (65K per account, supports 256 accounts)├── Region: /20 (4K per region, supports 16 regions per account)├── AZ: /22 (1K per AZ, supports 4 AZs per region)└── Subnet: /24-/28 (varies by workload)Each level must have enough space to contain all children. Use the prefixLengths configuration to control this:
{ "baseCidr": "10.0.0.0/8", "prefixLengths": { "account": 16, "region": 20, "az": 22 }}Rule of thumb: Each level’s prefix should be at least 2-4 bits larger than the parent to allow for growth.
| Parent | Child | Bits Difference | Children Supported |
|---|---|---|---|
| /8 | /16 | 8 bits | 256 |
| /16 | /20 | 4 bits | 16 |
| /20 | /22 | 2 bits | 4 |
Even if you’re single-cloud today, reserve space for future cloud providers:
{ "baseCidr": "10.0.0.0/8", "accounts": [ { "name": "production", "clouds": { "aws": { "baseCidr": "10.0.0.0/12", "regions": ["us-east-1", "us-west-2"] } } } ]}This reserves 10.0.0.0/12 for AWS, leaving 10.16.0.0/12 through 10.240.0.0/12 available for Azure, GCP, or future expansion.
Different workloads have different IP requirements:
| Subnet Type | Prefix | Usable IPs | Use Case |
|---|---|---|---|
/22 | Large | 1,022 | Kubernetes node pools, container clusters |
/23 | Medium-Large | 510 | Application tier, microservices |
/24 | Standard | 254 | General purpose, web tier |
/25 | Medium | 126 | Smaller app deployments |
/26 | Small | 62 | Databases, caches |
/27 | Smaller | 30 | Management, monitoring |
/28 | Minimal | 14 | Load balancers, NAT gateways |
Cloud providers reserve IPs in each subnet beyond the standard network and broadcast addresses:
| Provider | Reserved IPs | What’s Reserved | Usable in /24 |
|---|---|---|---|
| AWS | 5 | Network, VPC router, DNS, future use, broadcast | 251 |
| Azure | 5 | Network, default gateway, 2× Azure DNS, broadcast | 251 |
| GCP | 4 | Network, gateway, second-to-last, broadcast | 252 |
Each cloud provider has specific constraints on VPC/VNet and subnet sizes:
| Provider | VPC/VNet Range | Subnet Range | Min Subnet | Notes |
|---|---|---|---|---|
| AWS | /16 to /28 | /16 to /28 | /28 (11 usable) | Most restrictive; use secondary CIDRs for larger VPCs |
| Azure | /2 to /29 | /2 to /29 | /29 (3 usable) | Very permissive; /8 recommended as practical max |
| GCP | /8 to /29 | /8 to /29 | /29 (4 usable) | Global VPC spans all regions |
{ "subnetTypes": { "Kubernetes": 22, "Private": 23, "Public": 24, "Data": 26, "Management": 28 }}Best for organizations with strict account boundaries (compliance, multi-tenant):
10.0.0.0/8 (Base)├── 10.0.0.0/16 - Account A (Development)├── 10.1.0.0/16 - Account B (Staging)├── 10.2.0.0/16 - Account C (Production)└── 10.3.0.0/16 - Account D (Shared Services)Benefits:
Best for organizations that want environment-level grouping:
10.0.0.0/8 (Base)├── 10.0.0.0/12 - Development Environments│ ├── 10.0.0.0/16 - Team A Dev│ └── 10.1.0.0/16 - Team B Dev├── 10.16.0.0/12 - Staging Environments└── 10.32.0.0/12 - Production EnvironmentsBenefits:
Best for multi-cloud organizations:
10.0.0.0/8 (Base)├── 10.0.0.0/12 - AWS├── 10.16.0.0/12 - Azure└── 10.32.0.0/12 - GCPBenefits:
Use consistent, descriptive names:
| Pattern | Examples | Best For |
|---|---|---|
| Environment | prod, staging, dev | Simple organizations |
| Team + Environment | platform-prod, data-dev | Multi-team organizations |
| Business Unit | finance, marketing, engineering | Large enterprises |
| Project | project-alpha, initiative-beta | Project-based organizations |
Choose names that reflect purpose:
{ "subnetTypes": { "Public": 24, "Private": 24, "Protected": 25, "Isolated": 26 }}{ "subnetTypes": { "LoadBalancer": 28, "WebServers": 24, "AppServers": 23, "Databases": 26, "Cache": 27 }}{ "subnetTypes": { "DMZ": 24, "Trusted": 23, "Restricted": 25, "HighSecurity": 26 }}network-configs/├── production.json├── staging.json├── development.json└── allocations/ ├── production.csv ├── staging.csv └── development.csv# Create a branch for network changesgit checkout -b add-new-region
# Edit configurationvim production.json
# Generate and verifysubnetter generate -c production.json -o allocations/production.csv
# Commit with descriptive messagegit add .git commit -m "feat(network): add ap-southeast-1 region to production"
# Create PR for reviewgit push origin add-new-regiongit tag -a v1.2.0 -m "Production network v1.2.0 with AP Southeast region"git push origin v1.2.0❌ Starting Too Small
Using /16 as base CIDR when /8 would allow future growth without re-IP.
❌ Inconsistent Patterns
Different allocation patterns across accounts makes automation and troubleshooting harder.
❌ No Reserved Space
Allocating every available block leaves no room for new regions or accounts.
❌ Overlapping Overrides
Using custom baseCidr values that overlap with auto-allocated ranges.
If you’re running out of space:
/26 instead of /24)If you have overlapping ranges:
subnetter validate -c config.json -vlocals { # Parse the Subnetter CSV output allocations = csvdecode(file("${path.module}/allocations.csv"))
# Filter for AWS subnets only aws_subnets = { for row in local.allocations : "${row["Account Name"]}-${row["Availability Zone"]}-${row["Subnet Role"]}" => { account = row["Account Name"] vpc_cidr = row["VPC CIDR"] az_cidr = row["AZ CIDR"] subnet_cidr = row["Subnet CIDR"] az = row["Availability Zone"] role = row["Subnet Role"] region = row["Region Name"] usable_ips = row["Usable IPs"] } if row["Cloud Provider"] == "aws" }}
resource "aws_subnet" "this" { for_each = local.aws_subnets
vpc_id = aws_vpc.main.id cidr_block = each.value.subnet_cidr availability_zone = each.value.az
tags = { Name = each.key Role = each.value.role Account = each.value.account UsableIPs = each.value.usable_ips }}name: Network Validation
on: pull_request: paths: - 'network-configs/**'
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm install -g subnetter - run: | for config in network-configs/*.json; do subnetter validate -c "$config" done