Managing Azure Virtual Network with Terraform
Learn how to create and manage Azure Virtual Networks using Terraform, including subnets, network security groups, and peering
Managing Azure Virtual Network with Terraform
Azure Virtual Network (VNet) is the fundamental building block for private networking in Azure. This guide covers provisioning a VNet, subnets, network security groups, and VNet peering with Terraform.
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Resource group created
Project Structure
terraform-azure-vnet/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
└── vnet/
├── main.tf
├── variables.tf
└── outputs.tf
Virtual Network Configuration
Create modules/vnet/main.tf:
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
address_space = [var.address_space]
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
}
resource "azurerm_subnet" "app" {
name = "${var.project_name}-app-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.app_subnet_prefix]
}
resource "azurerm_subnet" "data" {
name = "${var.project_name}-data-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.data_subnet_prefix]
service_endpoints = ["Microsoft.Storage", "Microsoft.Sql"]
}
Network Security Groups
resource "azurerm_network_security_group" "app" {
name = "${var.project_name}-app-nsg"
location = var.location
resource_group_name = var.resource_group_name
security_rule {
name = "AllowHTTPS"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "DenyAllInbound"
priority = 4096
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = var.tags
}
resource "azurerm_subnet_network_security_group_association" "app" {
subnet_id = azurerm_subnet.app.id
network_security_group_id = azurerm_network_security_group.app.id
}
VNet Peering
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
name = "hub-to-spoke"
resource_group_name = var.hub_resource_group_name
virtual_network_name = var.hub_vnet_name
remote_virtual_network_id = azurerm_virtual_network.main.id
allow_virtual_network_access = true
allow_forwarded_traffic = true
}
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "spoke-to-hub"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
remote_virtual_network_id = var.hub_vnet_id
allow_virtual_network_access = true
allow_forwarded_traffic = true
}
Outputs
output "vnet_id" {
value = azurerm_virtual_network.main.id
}
output "app_subnet_id" {
value = azurerm_subnet.app.id
}
output "data_subnet_id" {
value = azurerm_subnet.data.id
}
Best Practices
-
Address planning
- Size address spaces for growth, but avoid overlap with on-premises or peered networks
- Reserve a dedicated subnet per Azure service that requires one (e.g.
AzureFirewallSubnet,GatewaySubnet)
-
Security
- Attach an NSG to every subnet, not just the VNet
- Default-deny inbound traffic and allow only what’s needed
- Use service endpoints or Private Link instead of exposing PaaS services publicly
-
Peering
- Peering is not transitive — a hub-and-spoke topology needs explicit peering between spokes if they must talk directly
- Enable
allow_forwarded_trafficwhen routing through a network virtual appliance in the hub
Conclusion
Terraform makes VNet topology reproducible and reviewable as code. Start with a minimal address plan, attach NSGs at the subnet level, and add peering only where cross-VNet traffic is actually required.