VPC Design & CIDR Planning
This article covers designing and creating an Amazon VPC with proper CIDR block selection and IP address allocation. We'll walk through the planning process and Terraform implementation.
CIDR Block Selection
Understanding CIDR Notation
CIDR (Classless Inter-Domain Routing) notation defines an IP address range using a prefix and subnet mask:
10.0.0.0/16
│ │ │ │
│ │ │ └─ Subnet mask (16 bits = /16)
│ │ └─── Network portion
│ └─────── Host portion
└─────────── IP address range
Common CIDR blocks for VPCs:
-
/16- 65,536 IP addresses (10.0.0.0 to 10.0.255.255) -
/20- 4,096 IP addresses (10.0.0.0 to 10.0.15.255) -
/24- 256 IP addresses (10.0.0.0 to 10.0.0.255)
Choosing the Right CIDR Block
Factors to consider:
- Current needs: How many resources do you need now?
- Future growth: Plan for 2-3x current capacity
- Subnet requirements: Each subnet needs its own IP range
- AWS reserved IPs: AWS reserves 5 IPs per subnet (first 4 + last 1)
Size recommendations:
-
Small projects:
/24(256 IPs) - development or small applications -
Medium projects:
/20(4,096 IPs) - most production workloads -
Large projects:
/16(65,536 IPs) - enterprise-scale deployments (recommended for production)
Private IP Ranges
Use RFC 1918 private IP address ranges:
- 10.0.0.0/8 - 16,777,216 addresses (10.0.0.0 to 10.255.255.255)
- 172.16.0.0/12 - 1,048,576 addresses (172.16.0.0 to 172.31.255.255)
- 192.168.0.0/16 - 65,536 addresses (192.168.0.0 to 192.168.255.255)
Best practice: Start with 10.0.0.0/16 for most use cases.
Avoiding IP Conflicts
Important considerations:
- Don't overlap with existing VPCs if you plan to peer them
- Don't overlap with on-premises networks if using VPN/Direct Connect
- Don't overlap with other AWS services you might connect to
Example conflict scenarios:
❌ VPC A: 10.0.0.0/16
❌ VPC B: 10.0.0.0/16 (Overlaps - cannot peer)
✅ VPC A: 10.0.0.0/16
✅ VPC B: 10.1.0.0/16 (No overlap - can peer)
IP Address Allocation Strategy
Subnet Planning
When planning subnets, consider:
- Number of availability zones: Typically 2-3 AZs for high availability
- Public subnets: Need IPs for ALBs, NAT Gateways
- Private subnets: Need IPs for ECS tasks, databases, internal services
- Reserve IP ranges: Don't allocate all IPs immediately - leave room for growth
Example Allocation
For a /16 VPC (10.0.0.0/16) with 3 availability zones:
VPC: 10.0.0.0/16 (65,536 IPs total)
Availability Zone A:
- Public Subnet: 10.0.1.0/24 (256 IPs)
- Private Subnet: 10.0.11.0/20 (4,096 IPs)
Availability Zone B:
- Public Subnet: 10.0.2.0/24 (256 IPs)
- Private Subnet: 10.0.21.0/20 (4,096 IPs)
Availability Zone C:
- Public Subnet: 10.0.3.0/24 (256 IPs)
- Private Subnet: 10.0.31.0/20 (4,096 IPs)
Reserved for future:
- 10.0.4.0/16 to 10.0.10.0/16
- 10.0.12.0/16 to 10.0.20.0/16
- etc.
AWS Reserved IPs
AWS reserves 5 IP addresses per subnet:
- First 4 IPs: Network address, VPC router, DNS server, reserved
- Last 1 IP: Broadcast address
Example for 10.0.1.0/24:
-
10.0.1.0- Network address (reserved) -
10.0.1.1- VPC router (reserved) -
10.0.1.2- DNS server (reserved) -
10.0.1.3- Reserved for future use -
10.0.1.4to10.0.1.254- Available for use (251 IPs) -
10.0.1.255- Broadcast address (reserved)
Terraform Implementation
Basic VPC Creation
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "content-delivery-vpc"
}
}
Key parameters:
-
cidr_block: The primary CIDR block for your VPC -
enable_dns_hostnames: Enables DNS hostnames for instances -
enable_dns_support: Enables DNS resolution within the VPC
Using Variables for Flexibility
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
default = ["ap-southeast-2a", "ap-southeast-2b"]
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "content-delivery-vpc"
}
}
Subnet CIDR Calculation
locals {
# Calculate subnet CIDRs
public_subnet_cidrs = [
cidrsubnet(var.vpc_cidr, 8, 1), # 10.0.1.0/24
cidrsubnet(var.vpc_cidr, 8, 2), # 10.0.2.0/24
]
private_subnet_cidrs = [
cidrsubnet(var.vpc_cidr, 8, 11), # 10.0.11.0/24
cidrsubnet(var.vpc_cidr, 8, 12), # 10.0.12.0/24
]
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = local.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "public-subnet-${count.index + 1}"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = local.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "private-subnet-${count.index + 1}"
}
}
Understanding cidrsubnet function:
cidrsubnet(prefix, newbits, netnum)
-
prefix: Base CIDR block (e.g., "10.0.0.0/16") -
newbits: Additional bits for subnet mask (8 = /24) -
netnum: Subnet number (0, 1, 2, etc.)
Example:
cidrsubnet("10.0.0.0/16", 8, 1) # Returns "10.0.1.0/24"
cidrsubnet("10.0.0.0/16", 8, 2) # Returns "10.0.2.0/24"
cidrsubnet("10.0.0.0/16", 4, 11) # Returns "10.0.11.0/20"
Design Best Practices
1. Use Consistent Numbering
Recommended pattern:
- Public subnets:
10.0.1.0/24,10.0.2.0/24,10.0.3.0/24 - Private subnets:
10.0.11.0/24,10.0.12.0/24,10.0.13.0/24 - Reserved ranges:
10.0.4.0/16to10.0.10.0/16for future use
This makes IP allocation predictable and easier to manage.
2. Plan for Multiple Environments
Separate VPCs per environment:
- Development:
10.1.0.0/16 - Staging:
10.2.0.0/16 - Production:
10.0.0.0/16
This prevents conflicts and allows for VPC peering if needed.
3. Document Your Allocation
Keep a record of your IP allocation for reference and troubleshooting.
Common Mistakes to Avoid
1. Too Small CIDR Block
❌ Don't use /28 or smaller - You'll run out of IPs quickly
✅ Use /24 minimum for subnets, /16 for VPC
2. Overlapping Subnets
❌ Don't create overlapping subnets:
subnet1 = "10.0.1.0/24" # 10.0.1.0 to 10.0.1.255
subnet2 = "10.0.1.128/25" # 10.0.1.128 to 10.0.1.255 (OVERLAPS!)
✅ Use non-overlapping ranges:
subnet1 = "10.0.1.0/24" # 10.0.1.0 to 10.0.1.255
subnet2 = "10.0.2.0/24" # 10.0.2.0 to 10.0.2.255 (No overlap)
3. Not Planning for Growth
❌ Don't allocate all IPs immediately - You'll need to recreate subnets later
✅ Reserve IP ranges for future expansion - Leave gaps in your numbering scheme
4. Ignoring AWS Reserved IPs
❌ Don't assume all IPs in a subnet are available
✅ Account for 5 reserved IPs per subnet in your calculations
What's Next?
In the upcoming articles, we'll dive deeper into:
- Subnet Architecture - Subnet configuration, route tables, and network ACLs implementation
- NAT Gateways & Internet Gateways - Internet Gateway and NAT Gateway setup, placement strategies, and cost optimization
- VPC Flow Logs & Network Monitoring - Flow log configuration, log analysis, and network troubleshooting
Repositories:
- terraform-aws-content-delivery-stack - Infrastructure code
- geomag-web-image - Web dashboard container image
- geomag-api-image - API service container image
Top comments (0)