Optimizing IP allocation in Terraform can be challenging, especially when dealing with dynamic subnetting. The cidrsubnet
function helps streamline this process by programmatically dividing CIDR blocks without manual calculations.
In this article, we dive into the practical use of the Terraform cidrsubnet
function to ensure scalable, well-organized network architectures.
What we cover:
CIDR (Classless Inter-Domain Routing) notation is a method used to represent IP addresses and their associated network prefixes. It consists of an IP address followed by a forward slash and a number indicating the subnet mask length. It is written as:
IP address/prefix length
CIDR helps efficiently allocate IP addresses and reduce routing table sizes by enabling the aggregation of multiple IP networks. It is widely used in networking, including IPv4 and IPv6, for defining network ranges in routing and firewall rules.
Here’s an example of an IP address range in CIDR notation: 192.168.1.0/24
. It represents the IP range from 192.168.1.0 to 192.168.1.255.
In IPV4, 32 bits are available, and every number corresponds to 8 bits. If you are using a /24, you define the network portion, leaving 8 bits available for the host addresses. This means that you will have 2 to the power of eight total IP addresses, which is 256 total IP addresses; only 254 will be usable (since the first is the network address and the last is the broadcast address).
Similarly, if you are using /25, you define the network portion, meaning that you will have 7 bits for the host addresses. This translates to 2 to the power of 7 total IP addresses, resulting in 128 IP addresses (126 usable for the same reasons).
This also means that when you are building host addresses, the first bits will not change (for /24, you won’t be able to change the first 24 bits).
A broader range, such as 10.0.0.0/16
, covers 10.0.0.0 to 10.0.255.255, offering 65,536 addresses, is ideal for large networks.
On the other hand, a more compact range, like 192.168.1.128/25
focuses on a smaller segment, stretching from 192.168.1.128 to 192.168.1.255, with 128 addresses, making it perfect for smaller subnet allocations.
Subnetting allows Terraform to define and manage logical subdivisions of a larger network, ensuring optimal resource allocation, which is essential for network segmentation, security, and efficient IP address management.
By subnetting, you can:
- Enhance security by isolating different workloads (e.g., separating public-facing services from internal databases).
- Optimize resource allocation by controlling IP address distribution and preventing exhaustion.
- Improve network performance by reducing congestion and latency within specific segments.
- Enable high availability by distributing resources across multiple subnets in different availability zones.
- Follow cloud-specific best practices since AWS, Azure, and GCP require subnetting for VPCs to manage networking properly.
The cidrsubnet
function in Terraform is used to generate a subnet from an existing CIDR block. It allows you to divide a larger network range into smaller subnets by specifying a new subnet prefix length and an index to create unique subnets. This function is ideal for infrastructure automation, especially in cloud networking, where subnet segmentation is frequently required.
The syntax is as follows:
cidrsubnet(prefix, newbits, netnum)
- prefix: The base CIDR block (e.g.,
"10.0.0.0/16"
). - newbits: The number of additional bits to extend the subnet mask.
- netnum: A number used to determine the offset of the subnet based on the new bits added, affecting the calculated network address.
For example, if you have a /16
CIDR block and need multiple /24
subnets, cidrsubnet
helps generate them dynamically instead of manually defining each subnet.
Let’s consider some examples.
Example 1: Creating four /26 subnets from a /24 network
Suppose we have a /24
network (192.168.1.0/24
) and we need to divide it into four /26
subnets.
variable "base_cidr" {
default = "192.168.1.0/24"
}
output "subnet_1" {
value = cidrsubnet(var.base_cidr, 2, 0)
}
output "subnet_2" {
value = cidrsubnet(var.base_cidr, 2, 1)
}
output "subnet_3" {
value = cidrsubnet(var.base_cidr, 2, 2)
}
output "subnet_4" {
value = cidrsubnet(var.base_cidr, 2, 3)
}
Since we are adding 2 bits (newbits = 2
), the new subnet mask becomes /26
, effectively dividing the original /24
subnet into 2² = 4
smaller subnets. The four generated subnets will be:
192.168.1.0/26
192.168.1.64/26
192.168.1.128/26
192.168.1.192/26
Example 2: Splitting a /16 network into /20 subnets
If you start with a /16
block (10.0.0.0/16
) and want to create multiple /20
subnets, you need to add 4 bits (newbits = 4
).
variable "base_cidr" {
default = "10.0.0.0/16"
}
output "subnet_1" {
value = cidrsubnet(var.base_cidr, 4, 0)
}
output "subnet_2" {
value = cidrsubnet(var.base_cidr, 4, 1)
}
Each /20
subnet will have 4096 IPs. The first two subnets will be:
10.0.0.0/20
10.0.16.0/20
Terraform automatically increments the network portion based on the netnum
value.
Example 3: Allocating a /28 Subnet from a /24 Block for a Small Service
For a small private network within 192.168.100.0/24
, if we want to allocate a /28
subnet for a service, we increase the mask by 4 bits (newbits = 4
).
variable "base_cidr" {
default = "192.168.100.0/24"
}
output "service_subnet" {
value = cidrsubnet(var.base_cidr, 4, 3)
}
This calculation results in the subnet 192.168.100.48/28
, which provides 14 usable IPs (since a /28
subnet has 16 total addresses, with two reserved for network and broadcast addresses).
The cidrsubnets()
function in Terraform allows you to efficiently generate multiple subnets at once by returning a tuple of subnet CIDR blocks from a single function call. It simplifies the process compared to multiple cidrsubnet()
calls.
In this example, we start with a /24
network (192.168.10.0/24
) and split it into three /26
subnets using cidrsubnets()
.
variable "base_cidr" {
default = "192.168.10.0/24"
}
output "subnets" {
value = cidrsubnets(var.base_cidr, 2, 2, 2)
}
The generated subnets would be:
192.168.10.0/26
192.168.10.64/26
192.168.10.128/26
The function generates subnets in sequence based on bitwise calculations; it does not inherently prevent overlaps unless the inputs are configured correctly. Creating multiple subnets this way is more efficient and readable than using multiple cidrsubnet()
calls separately.
Using CIDR notation and Terraform’s cidrsubnets()
, you can efficiently plan a network topology that scales over time.
Imagine a data center with a 10.0.0.0/16
private network. The goal is to segment it into different functional subnets using CIDR notation. We’ll create separate subnets for servers, databases, and user devices, ensuring efficient IP allocation.
The base network 10.0.0.0/16
allows for 65,536 IPs. This is too large for direct usage, so we divide it into smaller blocks.
Using Terraform’s cidrsubnets()
function, we can split the /16
network into three /18 subnets (each containing 16,384 IPs), assigned to different purposes.
Each /18
subnet provides a huge IP range while maintaining logical separation:
10.0.0.0/18
→ Server network (Used for web and application servers)10.0.64.0/18
→ Database network (Dedicated for databases)10.0.128.0/18
→ User devices (For employees and IoT devices)
Let’s say the Server Network (10.0.0.0/18
) needs further segmentation. We can divide it into four /20
subnets using:
output "server_subnets" {
value = cidrsubnets("10.0.0.0/18", 2, 2, 2, 2)
}
This creates:
10.0.0.0/20
→ Web servers10.0.16.0/20
→ App servers10.0.32.0/20
→ API servers10.0.48.0/20
→ Load balancers
To automate multiple subnets dynamically in Terraform, you can use the for
loop with the cidrsubnet
function inside a for_each
or count
.
Suppose you have a /16
VPC CIDR (10.0.0.0/16
) and want to generate five /24
subnets dynamically.
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
variable "subnet_count" {
default = 5
}
resource "aws_subnet" "subnets" {
count = var.subnet_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index % length(var.availability_zones)]
tags = {
Name = "Subnet-${count.index}"
}
}
The cidrsubnet(var.vpc_cidr, 8, count.index)
function generates /24
subnets within the /16
VPC. Since 8 additional bits convert /16
into /24
, each subnet gets 256 IPs.
Using count.index % length(var.availability_zones)
ensures that if there are fewer availability zones than subnets, the subnets are evenly distributed without causing an error.
If subnet_count = 5
, the function creates:
10.0.0.0/24
10.0.1.0/24
10.0.2.0/24
10.0.3.0/24
10.0.4.0/24
This method scales easily, reducing manual effort in managing large networks.
The Terraform cidrsubnet
function helps break a parent CIDR block into smaller subnets by specifying a new subnet mask and index. It’s useful for structuring VPC networks, ensuring consistent subnet sizing, and automating IP allocation.
We encourage you to explore how Spacelift makes it easy to work with Terraform. If you need help managing your Terraform infrastructure, building more complex workflows based on Terraform, and managing AWS credentials per run, instead of using a static pair on your local machine, Spacelift is a fantastic tool for this.
If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Discover better way to manage Terraform
Spacelift helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and many more.