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

  1. 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)
  2. 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
  3. Peering

    • Peering is not transitive — a hub-and-spoke topology needs explicit peering between spokes if they must talk directly
    • Enable allow_forwarded_traffic when 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.

Additional Resources