Deploying Azure Virtual WAN using Terraform


Recently I have been working with Azure Virtual WAN to provide connectivity between Azure Regions, Branch Offices, and Remote Users. Azure Virtual WAN is a networking service that brings many of the commonly found connectivity services into a single area, providing simplified connectivity and management. These services include:

    • Branch connectivity.
    • Site-to-site VPN connectivity.
    • Remote user VPN connectivity (point-to-site).
    • Private connectivity (ExpressRoute).
    • Intra-cloud connectivity (transitive connectivity for virtual networks).
    • VPN ExpressRoute inter-connectivity.
    • Routing, Azure Firewall, and encryption for private connectivity.

You can read more about Azure Virtual WAN on the Microsoft Documentation page here:

vWAN Diagram from Microsoft Docs Site.
Azure Virtual WAN overview from the Microsoft Docs Website

Azure Virtual WAN is a great solution for multi-region deployments, and simplifies deployment where branch or remote user connectivity is required, by bringing all the features needed into a single area.

Most of the deployments I have been working on have been utilising Terraform to conduct the deployment, and that will be the focus of this post.

Why use Virtual WAN?

Virtual WAN provides an integrated approach to connectivity – bringing many features to a single area within Azure, and providing additional levels of automation and control. Use cases like branch and remote user connectivity are obvious, but also there are options that Virtual WAN enables – creating new Resource Deployments rapidly in new Regions for example, by virtue of the easily expanded connectivity. Technologies like Citrix and other third party solutions can benefit from Virtual WAN too. In many cases it is simply about getting a connectivity baseline into your Cloud estate – and in the case of Virtual WAN, a solution that offers easily expanded and branch site connectivity with ease.

Deploying Virtual WAN with Terraform – Demo Lab

To deploy Azure Virtual WAN with Terraform, we will be using the AzureRM provider. If you need a refresher on getting started or using Terraform with Azure, check out my series on this here. Within this post, I’ll be breaking down the steps taken to create my Virtual WAN Demo Environment, available within my GitHub Repo.

✅ This environment is a simple starting point for Virtual WAN – it is not intended for production use!

This environment deploys the following:

    1. A Resource Group in each of the two Azure Regions.
    2. A Virtual WAN in the Primary Region
    3. A Virtual WAN Hub in two Azure Regions
    4. A VNet in each Azure Region which is connected to the Virtual WAN Hub.
    5. A Subnet and NSG in each of the above VNets.
    6. A KeyVault, with an administrator password (generated randomly by Terraform) for the VMs.
    7. A Virtual Machine in each Azure Region (in the Regional VNets), to allow testing of Connectivity. Note: these VMs have a Public IP and RDP is enabled for easy testing access.
    8. A Custom Script Extension that runs on both VMs to add a few testing Apps (using Chocolatey) and allows ICMP through Windows Firewall for testing connectivity over Virtual WAN.

The diagram below shows the Lab environment:

vWAN Demo Lab
Virtual WAN Demo Lab Environment

The concept behind the Lab (other than demonstrating Virtual WAN using Terraform), is that whilst direct RDP to VMs would not normally be enabled, this allows for easy testing – so we can RDP to the VMs and prove connectivity between them over Azure Virtual WAN.

vWAN diagram showing traffic paths
Communication between Spokes and VMs across Regions using Azure Virtual WAN

As you can see in the below diagram, Spoke to Spoke communication (so, in our case from VM to VM across Regions) will transit via Virtual WAN:

Virtual WAN Terraform – The Building Blocks

There are a number of important blocks of code we will need to define from the AzureRM Provider to create the Virtual WAN environment.

⚠ Note: the below aspects only cover the Virtual WAN elements, I’ve omitted aspects like the Virtual Machines – but you can download the full lab from my GitHub Repo if you’d rather just deploy it as a whole environment instead!

We need to create 3 core elements to setup Virtual WAN:

    • Virtual WAN
    • Virtual WAN Hubs
    • Virtual WAN Hub Connections to Spoke VNets

Creating the Virtual WAN:

The first element that we need to create is the Virtual WAN:

vWAN Environment
Virtual WAN
# Virtual WAN
resource "azurerm_virtual_wan" "vwan1" {
  name                = "${var.lab-name}-vWAN-01"
  resource_group_name =
  location            = var.region1

  # Configuration 
  office365_local_breakout_category = "OptimizeAndAllow"

  tags = {
    Environment = var.environment_tag

Creating the Virtual WAN Hubs:

Virtual WAN Hubs are required in each Azure Region that we require connectivity within, so for this Lab environment we require two Virtual WAN Hubs:

vWAN Hubs
Virtual WAN Hubs
# Virtual WAN Hubs
resource "azurerm_virtual_hub" "region1-vhub1" {
  name                = "${var.region1}-vWAN-hub-01"
  resource_group_name =
  location            = var.region1
  virtual_wan_id      =
  address_prefix      = var.vwan-region1-hub1-prefix1

  tags = {
    Environment = var.environment_tag
resource "azurerm_virtual_hub" "region2-vhub1" {
  name                = "${var.region2}-vWAN-hub-02"
  resource_group_name =
  location            = var.region2
  virtual_wan_id      =
  address_prefix      = var.vwan-region2-hub1-prefix1

  tags = {
    Environment = var.environment_tag

Creating the Hub to Spoke Connections:

To allow our VNet Spokes to communicate via Virtual WAN, we need to create a connection in each Region between the Virtual WAN Hub and Spoke VNet:

vWAN Hub-Spoke Connections
Virtual WAN Hub-Spoke Connections
# Virtual WAN Connections
resource "azurerm_virtual_hub_connection" "region1-connection1" {
  name                      = "${var.region1}-conn-vnet1-to-vwan-hub"
  virtual_hub_id            =
  remote_virtual_network_id =
resource "azurerm_virtual_hub_connection" "region2-connection1" {
  name                      = "${var.region2}-conn-vnet1-to-vwan-hub"
  virtual_hub_id            =
  remote_virtual_network_id =

✅ To secure internet traffic and force this out via Azure Firewall, add the following line to the azurerm_virtual_hub_connection block. You will need a Secure Virtual Hub with Azure Firewall to achieve this.

internet_security_enabled = true

I want Azure Firewall too!

Should you wish to include Azure Firewall within the Virtual WAN Topology you can include this to create Secure Virtual Hubs, Firewalls, and Firewall Policies.


To create an Azure Firewall instance, use the code below – note that there is a difference in the “sku_name” and this is now referenced as “AZFW_Hub”.

# Firewalls 
resource "azurerm_firewall" "region1-fw01" {
  name                = "${var.region1}-fw01"
  location            = var.region1
  resource_group_name =
  sku_tier            = "Premium"
  sku_name            = "AZFW_Hub"
  firewall_policy_id  =
  virtual_hub {
    virtual_hub_id =
    public_ip_count = 1

Firewall Policy & Ruleset

You will also need a Firewall Policy in place, to allow for traffic control. To create a Firewall Policy, use the code below:

#Firewall Policy
resource "azurerm_firewall_policy" "fw-pol01" {
  name                = "fw-pol01"
  resource_group_name =
  location            = var.region1
# Firewall Policy Rules
resource "azurerm_firewall_policy_rule_collection_group" "region1-policy1" {
  name               = "fw-pol01-rules"
  firewall_policy_id =
  priority           = 100

  network_rule_collection {
    name     = "network_rules1"
    priority = 100
    action   = "Allow"
    rule {
      name                  = "network_rule_collection1_rule1"
      protocols             = ["TCP", "UDP", "ICMP"]
      source_addresses      = ["*"]
      destination_addresses = ["*"]
      destination_ports     = ["*"]

⚠ Note: by default, Private Traffic is NOT routed via Azure Firewall. You must enable this in the Portal. At the time of writing there does not appear to be a way to enable this using the AzureRM Terraform Provider.  

Use this drop down to force Hub Traffic via Azure Firewall
Use this drop down to force Private Network Hub Traffic via Azure Firewall. You will need to do this for ALL Hubs in the Topology.

✅Note: I would recommend reading this link around multi-region Virtual WAN and Azure Firewall deployment as there are currently some additional considerations:

What about Connectivity?

Azure Virtual WAN also provides a range of connectivity options, supporting Site-to-Site VPNs, Point to Site VPNs, ExpressRoute and third-party integrations. I’ve outlined the Terraform required for Site-to-Site, Point to Site, and ExpressRoute below:

Site-to-Site Connectivity

To create a Site-to-Site connection, you will initially need a VPN Gateway. Remember, you will need one of these in each Virtual WAN Hub you wish to connect into. You can create this using the code below:

resource "azurerm_vpn_gateway" "region1-gateway1" {
  name                = "${var.region1}-vpngw-01"
  location            = var.region1
  resource_group_name =
  virtual_hub_id      =

Once the Gateway is in place, you can create a VPN Site. These sites represent the Physical locations you wish to connect to:

resource "azurerm_vpn_site" "region1-officesite1" {
  name                = "${var.region1}-officesite-01"
  location            = azurerm_resource_group.region1-rg1.location
  resource_group_name =
  virtual_wan_id      =
  address_cidrs = [""]
  link {
    name       = "Office-Link-1"
    ip_address = ""
    speed_in_mbps = "20"

Once the site is created, you can then define the connection:

resource "azurerm_vpn_gateway_connection" "region1-officesite1" {
  name               = "${var.region1}-officesite1-conn"
  vpn_gateway_id     =
  remote_vpn_site_id =

  vpn_link {
    name             = "link1"
    vpn_site_link_id =[0].id

⚠ Note, you will also need to define a number of connection specific aspects here too, like connection modes/names/protocols etc. See here:

Point to Site Connectivity

To create a Point to Site Connection, we need two items in place: a Gateway and a Configuration. To create the Gateway and Configuration, use the code below:

resource "azurerm_point_to_site_vpn_gateway" "region1-p2s-01" {
  name                        = "${var.region1}-p2s-01"
  location                    = var.region1
  resource_group_name         =
  virtual_hub_id              =
  vpn_server_configuration_id =
  scale_unit                  = 1
  connection_configuration {
    name = "${var.region1}-p2s-01"

    vpn_client_address_pool {
      address_prefixes = [
resource "azurerm_vpn_server_configuration" "region1-p2s-conn-01" {
  name                     = "${var.region1}-p2s-conn-01"
  resource_group_name      =
  location                 = var.region1
  vpn_authentication_types = ["AAD"]

  azure_active_directory_authentication {
    audience = "GUID-goes-here"
    issuer   = ""
    tenant   = "GUID-goes-here"

⚠ Note: You may need to customise the above depending on your required configuration, see here:


If you wish to utilised ExpressRoute with Virtual WAN, you will need to create an ExpressRoute Gateway within your Hub using the code below:

resource "azurerm_express_route_gateway" "region1-er-gateway-01" {
  name                = "${var.region1}-er-gateway-01"
  resource_group_name =
  location            = var.region1
  virtual_hub_id      =
  scale_units         = 1

  tags = {
    environment = "Production"

Once this Gateway is in place the ExpressRoute Circuit, Peering and Connection can be created. There are range of options you can specify here, and for obvious reasons ExpressRoute is not available within my personal Lab environment (I wish! 😂), so I will just provide links to the relevant AzureRM Provider Sections instead:


Hopefully this post was helpful in running through the Terraform required to deploy Azure Virtual WAN, and providing a small testing Lab. If you have any questions please feel free to reach out via my contact page or twitter! ☺

Skip to content