Infrastructure defined with Terraform frequently needs to model and manage IP address space in a precise, repeatable, and environment-aware way. Whether provisioning cloud networks, creating application subnets, assigning host addresses, or integrating with existing on-premises systems, Terraform configurations must be able to handle IP network addressing dynamically. This is why CIDR address manipulation functionality is a foundational part of infrastructure as code.
Common real-world use cases include dynamically deriving subnets from a larger address block, calculating host IP addresses for gateways or services, validating that a network can support a required number of hosts, and generating consistent subnet layouts across multiple environments or regions. Hard-coding IP configuration quickly becomes unmanageable as infrastructure grows, environments diverge, or address plans evolve. Terraform's built-in IP network functions allow these calculations to be expressed declaratively, ensuring network layouts remain deterministic, scalable, and self-documenting.
Terraform provides a small but powerful set of IP and CIDR manipulation functions designed specifically
for these tasks. Functions such as cidrhost, cidrnetmask, cidrsubnet,
and cidrsubnets enable configurations to derive host addresses, compute network masks, and
partition address space into subnets directly within Terraform expressions. These functions operate on
standard CIDR notation and enforce strict validation rules, helping catch addressing errors early during
plan and apply phases.
This article explains how Terraform's IP network and CIDR functions work, where they are most useful, and where they can be unintuitive or challenging. Through practical examples and analysis you will learn how to apply these functions effectively, understand their parameter semantics, and make informed decisions when designing subnetting logic for complex environments.
Terraform's host-level CIDR functions are designed to assist with a common class of tasks at the individual host and network boundary level, making them ideal for calculating gateway addresses, service IPs, broadcast addresses, and validating subnet capacity.
The two primary functions in this category are cidrhost and cidrnetmask.
Together, they allow Terraform configurations to derive host addresses and subnet masks
directly from a CIDR prefix.
cidrhost: Calculating Host IP Addresses
The cidrhost function returns a specific IP address from a given CIDR block.
Where:
prefix is a CIDR-formatted network address (for example, 192.168.10.0/24)hostnum is an integer offset into the host address space ("host number")Terraform automatically performs the following:
/0../32)
Special hostnum values have defined meanings:
cidrhost(prefix, 0) - network addresscidrhost(prefix, -1) - broadcast addresscidrhost(prefix, 1) - first hostcidrhost(prefix, -2) - last hostPractical use cases:
hostnum = 1)cidrnetmask: Deriving Subnet Masks
The cidrnetmask function returns the subnet mask in a dotted-decimal notation for a
given CIDR prefix.
Where:
prefix is any valid CIDR prefix (for example, 192.168.10.0/24)This function is especially useful when interacting with providers that require subnet masks instead of CIDR notation.
This example demonstrates a comprehensive approach to host-level CIDR calculations in Terraform. It combines input validation, calculation of network properties, host selection, and binary/decimal visualization to demonstrate capabilities of Terraform host-level CIDR functions.
It does not provision any real infrastructure and is particularly useful as a learning and troubleshooting reference, rather than a practical code snippet. The complete code sample can be obtained from this repo.
Output:
How it all works:
The validation block for the net_cidr variable uses a locally
defined regular expression (local.regex_cidr) to ensure that the provided value
(var.net_cidr) conforms to the IPv4 CIDR notation.
The validation block for the host_num variable relies on the calculated
maximum number of usable hosts (local.max_host_num) to verify that the requested
host number (var.host_num) fits within the usable address range of the specified subnet
(var.net_cidr).
The configuration then computes a series of local values representing key network properties,
such as the network address, broadcast address, and the first and last usable host addresses,
using the cidrhost and cidrnetmask functions.
Each value is rendered in both decimal (format("%8d", b)) and binary
(format("%08b", b)) form, making it easier to visualize the addresses
structure at the bit level.
The example produces two structured outputs (output "net_info" { ... } and
output "host_ip" { ... }) that present network and host details in a clear,
easy to read format.
It is also worth noting that cidrhost(var.net_cidr, 0) automatically normalizes
the CIDR prefix by clearing the host portion of the input address. For example, the input
192.168.10.15/24 is treated as the network address 192.168.10.0/24,
with the host portion (.15) removed.
As this example demonstrates, Terraform's host-level CIDR functions are powerful and flexible tools for network analysis and validation. That said, for deeper inspection or visual confirmation, it is often helpful to complement Terraform with external tools, such as the Visual IP and Mask Calculator, to validate calculations and gain additional insight into subnet address structure.
In the next section, we will move beyond individual host calculations and explore subnet generation
using cidrsubnet and cidrsubnets, where Terraform's functionality becomes
more powerful and more nuanced.
cidrsubnet and cidrsubnets
While cidrhost and cidrnetmask operate at the host and network boundary level,
Terraform's cidrsubnet and cidrsubnets functions address a different class of
problems: partitioning an address space into smaller networks. These functions are fundamental when
designing repeatable VPC layouts, regional subnet structures, Kubernetes clusters, or
environment-specific network topologies.
Although the two functions are related, they differ significantly in how parameters are supplied and how results are produced. Understanding these differences is essential to using them correctly and avoiding network configuration errors.
cidrsubnet: Deriving One Subnet
The cidrsubnet function derives a single subnet from a base CIDR block.
Where:
prefix is the base CIDR block to subdivide.newbits is the number of additional prefix bits to add to the base network prefix.
This determines the prefix length ("size") of the generated subnet.netnum is a zero-based index selecting which subnet to return.
A key point is that newbits is not the target prefix length. Instead,
it represents the difference between the desired subnet prefix and the base prefix.
Such approach differs from how subnetting is often described in networking documentation and
can be a source of confusion for new users.
Example:
Here:
/16newbits = 8 yields /24netnum = 0 selects the first /24 subnetcidrsubnets: Producing Multiple Subnets
The cidrsubnets function generates multiple sequential subnets at once.
Where:
prefix is the base CIDR block to subdivide.newbits... is a list of newbits values. Each entry defines
the prefix length ("size") of the next subnet in sequence.
Example:
Here:
/16cidrsubnets("172.16.0.0/16", 8, 4, 8):
newbits = 8 yields /24 and selects the first /24 subnetnewbits = 4 yields /20 and selects the next available /20 subnetnewbits = 8 yields /24 and selects the next available /24 subnet172.16.1.0 - 172.16.15.255) between the first and the second subnets.cidrsubnets("172.16.0.0/16", 8, 8, 4):
newbits = 8 yields /24 and selects the first /24 subnetnewbits = 8 yields /24 and selects the next available /24 subnetnewbits = 4 yields /20 and selects the next available /20 subnet
Important characteristics of cidrsubnets:
newbits
This makes cidrsubnets very effective when:
However, it can become challenging when subnet counts vary by environment, or when it is required to reference subnets by names.
When working with cidrsubnets, it is often beneficial to complement Terraform with
online networking tools similar to this
IP Subnet Calculator.
Such tools provide a visual breakdown of how a parent CIDR block is partitioned into
smaller subnets and show network boundaries, address ranges, and broadcast addresses. With this
information, you can more easily plan subnet allocation and identify potential address space
fragmentation early in the design process. This is especially useful during preparation phases,
where understanding trade-offs between subnet size, growth capacity, and efficient IP space
utilization can help prevent costly network redesigns later.
The following example demonstrates how cidrsubnet can be used safely and predictably
to generate multiple subnets from a base CIDR block.
It does not provision any real infrastructure and can be particularly useful as a learning and troubleshooting reference. The complete code sample can be obtained from this repo.
Output:
How it all works:
The base CIDR (base_cidr) represents the parent network to be partitioned.
Rather than using newbits directly, the configuration works with prefix length
(subnet_pref_length), which is more intuitive for most users.
The locals:
base_pref_length - Extracts the base prefix lengthmax_subnets - Calculates how many subnets of the requested size can fit into the base CIDRnum_subnets and subnet_pref_length
The subnets_by_num output demonstrate how to use a for expression together
with cidrsubnet() to generate a specific number (num_subnets) of contiguous
subnets with a given prefix length (subnet_pref_length).
The subnets_by_name output demonstrate how to use a for expression together
with cidrsubnet() to generate a list of subnets with a given prefix length
(subnet_pref_length) based on a provided name or label list (subnet_list).
The demonstrated subnet generation approach is often preferred over cidrsubnets when:
As network designs grow in complexity, CIDR calculations (cidrsubnet, cidrsubnets)
directly embedded into root modules can quickly become difficult to maintain. This is where Terraform
modules for subnet allocation become especially valuable. A well-designed module encapsulates IP address
planning logic, enforces constraints, and presents a consistent interface to consumers.
Using a dedicated module for subnet allocation provides several important benefits:
The official HashiCorp subnets module is a good example of these principles in practice. It calculates subnet addresses based on the following inputs:
base_cidr_block - A network address prefix in CIDR notation that all of the requested
subnetwork prefixes will be allocated within.networks - A list of objects describing requested subnetwork prefixes, each including
a subnetwork name and new_bits specifying the number of additional network
prefix bits to add to the existing prefix on base_cidr_block.
Alternatively, if you prefer a more traditional approach utilizing prefix length notation (instead of
new_bits), model your code after the
get_subnets module presented in our demo repository.
This article explored how Terraform's built-in IP network and CIDR functions can be used to analyze, validate, and generate IP addressing schemes directly within infrastructure-as-code configuration.
We examined the following Terraform's core IP and CIDR-related functionality in detail:
cidrhost and cidrnetmask, showing
how to derive host IPs, normalize network prefixes, and extract netmask information.cidrsubnet and cidrsubnets,
highlighting how Terraform partitions address space and how its parameter model differs from
traditional subnet calculators.The key takeaway is that Terraform's IP network functions are powerful but low-level. They are ideal building blocks, but for anything beyond simple scenarios, combining them with input validation, external IP address visualization tools, and reusable modules results in fewer errors, and more scalable network designs.
Terraform's IP network functions - cidrhost, cidrsubnet, and cidrsubnets
work with both IPv4 and IPv6 CIDR blocks. From a functional perspective, the same concepts apply: Terraform
treats IPv6 CIDRs as larger address spaces and performs the same prefix-based calculations.
Note that cidrnetmask only accepts IPv4 addresses and returns an error if you use an IPv6
address.
There are some important differences and considerations when adapting the examples in this article for IPv6:
/64 or larger,
and while cidrhost still works, direct address allocation to hosts is rarely practical
or meaningful in IPv6 designs.cidrsubnet and cidrsubnets.
At a high level, Terraform's IP network functions, such as cidrhost, cidrsubnet,
and cidrsubnets, behave consistently across both IPv4 and IPv6. The primary differences lie
not in the functions themselves, but in the underlying design goals and the network planning patterns
that are specific to IPv4 versus IPv6 addressing models.
More Terraform Tutorials
Understanding Terraform Variable Precedence
Terraform Value Types Tutorial
Terraform count Explained with Practical Examples
Terraform for_each Tutorial with Practical Examples
Exploring Terraform dynamic Blocks with GCP Examples
Working with External Data in Terraform
Terraform Modules FAQ