Azure Lesson 58 of 137

Scaling Connectivity with Azure Virtual WAN: A Global Network Build

A hand-built hub-and-spoke works beautifully until you have five of them on three continents, a mesh of VNet peerings nobody can draw from memory, and an NVA whose route tables are load-bearing tribal knowledge. Azure Virtual WAN is Microsoft’s answer: a managed global backbone where hubs are a resource type, transit is automatic, and the routing you maintained by hand becomes declarative. This is the build, end to end.

In a nutshell

If you have ever wired two offices together, you know the drill: run a cable, configure both ends, and repeat for every new pair of offices. Three offices is three links; six offices is fifteen. Hand-built Azure networking has the same shape — every region you add means more VNet peerings, more VPN tunnels, and more route tables to keep in sync. Azure Virtual WAN replaces all of that hand-wiring with Microsoft’s own global backbone, rented as a managed service.

The mental model: picture each region as an airport, and Microsoft’s worldwide network as the flight routes that already exist between airports. Your job shrinks to deciding which cities get an airport — a Virtual Hub — and plugging the local buildings into the nearest one. Spoke VNets, branch offices (over VPN or SD-WAN), remote laptops (point-to-site), and private circuits (ExpressRoute) all connect to a hub. The routes between hubs you never build; Microsoft already runs them, and any hub can reach any other automatically. One hub-of-hubs mesh, instead of a spaghetti of peerings you drew on a whiteboard and prayed nobody would touch.

That is the whole promise: the hub, its router, and the transit between regions become the platform’s problem, and the topology you used to maintain by hand becomes a handful of declarative settings. The rest of this lesson is the end-to-end build — but if you remember only one thing, remember the airports.

Level: Advanced · Time: ~37 min

Before you start, you should be comfortable with:

After this lesson you will be able to:

When hub-and-spoke stops scaling and Virtual WAN takes over

Classic hub-and-spoke is a pattern you assemble: a hub VNet, a VPN or ExpressRoute gateway, an NVA or Azure Firewall, and User-Defined Routes (UDRs) on every spoke forcing traffic through it. One region is fine; the pain compounds when you go global:

Virtual WAN inverts the model. A Virtual Hub is a Microsoft-managed VNet containing a built-in router; you attach spokes, gateways, and firewalls as connections rather than wiring them. Hubs in different regions are joined by Microsoft’s backbone automatically, giving any-to-any transit — spoke-to-spoke, branch-to-branch, cross-region — without a single peering you maintain.

Use the Standard SKU. The Basic SKU only supports site-to-site VPN and gives you none of the transit, ExpressRoute, or routing features this article relies on. Basic exists for the smallest VPN-only cases; for an enterprise backbone it is a dead end.

Azure Virtual WAN global network: two secured Virtual Hubs (West Europe, East US) joined by the Microsoft global backbone, each with a managed router and Azure Firewall, attaching spoke VNets, branch SD-WAN, on-prem ExpressRoute and remote P2S users, with routing intent and route-table segmentation

The spokes, branches, and gateways stay yours. The hub, its router, and inter-hub transit are the platform’s problem now.

Step 1 - Virtual WAN topology: hubs, regions, and the global backbone

The Virtual WAN resource itself is just a logical container and a global setting; the hubs do the work. Provision one Virtual WAN, then a hub per region where you need a connectivity anchor.

LOCATION_EU=westeurope
LOCATION_US=eastus
RG=rg-vwan-global
VWAN=vwan-global

az group create --name $RG --location $LOCATION_EU

# Logical Virtual WAN container (Standard enables full transit + branch features)
az network vwan create \
  --resource-group $RG \
  --name $VWAN \
  --location $LOCATION_EU \
  --type Standard

# A managed hub per region. The /23 address space is internal to the hub
# and must NOT overlap any spoke, on-prem, or other hub range.
az network vhub create \
  --resource-group $RG \
  --vwan $VWAN \
  --name hub-weu \
  --location $LOCATION_EU \
  --address-prefix 10.100.0.0/23 \
  --sku Standard

az network vhub create \
  --resource-group $RG \
  --vwan $VWAN \
  --name hub-eus \
  --location $LOCATION_US \
  --address-prefix 10.101.0.0/23 \
  --sku Standard

Two address-planning rules will save you a rebuild:

  1. The hub prefix must be at least /24; /23 is the safe default. The hub packs a router, gateways, and firewall into that space, and you cannot resize it after creation. Undersize it and you cannot add a gateway later.
  2. Every prefix in the WAN must be globally unique — hubs, spokes, on-prem, branches. Virtual WAN advertises everything to everything by default; overlapping ranges produce black holes that are miserable to diagnose.

Hubs are joined the moment they exist under the same Virtual WAN. There is no peering step between hub-weu and hub-eus — that is the entire point.

Step 2 - Connecting spokes, branches (SD-WAN), and ExpressRoute

Spokes attach as hub virtual network connections. No peering, no gateway transit toggle, no UDRs to force traffic to the hub.

SPOKE_ID=$(az network vnet show -g rg-apps-weu -n vnet-app-weu --query id -o tsv)

az network vhub connection create \
  --resource-group $RG \
  --vhub-name hub-weu \
  --name conn-app-weu \
  --remote-vnet $SPOKE_ID

That connection alone gives the spoke transit to every other spoke and, across the backbone, to spokes behind hub-eus. Branch and ExpressRoute connectivity each need a gateway in the hub first. Gateways scale in scale units, not instance counts — one S2S VPN scale unit is roughly 500 Mbps aggregate, sized by total branch demand.

# Site-to-site VPN gateway in the hub (for IPsec branches / SD-WAN)
az network vpn-gateway create \
  --resource-group $RG \
  --name vpngw-weu \
  --location $LOCATION_EU \
  --vhub hub-weu \
  --vpn-gateway-scale-unit 2

# ExpressRoute gateway in the hub (scale unit ~ 2 Gbps each)
az network express-route gateway create \
  --resource-group $RG \
  --name ergw-weu \
  --location $LOCATION_EU \
  --virtual-hub hub-weu \
  --min-val 1 \
  --max-val 2

For branches, model each physical site as a VPN Site (its public IP and on-prem prefixes, or BGP ASN) and connect it to the VPN gateway. At scale you do not click these one by one — Virtual WAN integrates with SD-WAN partners (Cisco, VMware, Barracuda, Fortinet and others) whose controllers provision the sites and IPsec tunnels into the hub automatically. The hub becomes the single managed concentrator instead of an NVA you patch.

ExpressRoute circuits connect to the ExpressRoute gateway and immediately gain transit to every spoke and, via the backbone, to remote regions — a single circuit in Europe can reach US workloads. Branch and ExpressRoute prefixes propagate into the hub over BGP; there is no UDR step.

Step 3 - Secured virtual hubs with Azure Firewall and routing intent

A hub with a firewall in it is a secured virtual hub. Historically you secured one by writing custom route tables that pointed 0.0.0.0/0 and the RFC1918 ranges at the firewall. That still works, but routing intent is the modern, far cleaner mechanism: you declare intent and the platform programs every route table in the hub for you.

First, deploy Azure Firewall into the hub via a firewall policy.

# Firewall policy (define rules, threat intel, premium features here)
az network firewall policy create \
  --resource-group $RG \
  --name fwpol-global \
  --location $LOCATION_EU

# Azure Firewall anchored to the hub
az network firewall create \
  --resource-group $RG \
  --name azfw-weu \
  --location $LOCATION_EU \
  --vhub hub-weu \
  --firewall-policy fwpol-global \
  --sku AZFW_Hub \
  --tier Standard \
  --count 1

Then turn on routing intent. Two policy types exist: InternetTraffic (0.0.0.0/0) and PrivateTraffic (RFC1918 plus any custom private prefixes). Setting both to route through the firewall means every flow the hub sees — spoke-to-spoke, spoke-to-internet, branch-to-spoke, and cross-hub — is inspected, with no per-spoke UDRs anywhere.

{
  "routingPolicies": [
    {
      "name": "InternetTrafficPolicy",
      "destinations": ["Internet"],
      "nextHop": "<azure-firewall-resource-id>"
    },
    {
      "name": "PrivateTrafficPolicy",
      "destinations": ["PrivateTraffic"],
      "nextHop": "<azure-firewall-resource-id>"
    }
  ]
}

Routing intent is configured per hub — enable it on every secured hub. Once both policies route to the firewall, inter-hub traffic is inspected by the firewall in each hub it transits, so plan firewall capacity for east-west volume, not just internet egress.

This is the single biggest operational win of Virtual WAN: the security posture that used to be hundreds of brittle UDRs becomes two declarative policies the platform keeps consistent.

Step 4 - Custom route tables and controlling any-to-any traffic flow

Any-to-any is the default, and for many estates that is exactly wrong. You usually want isolation — production cannot reach development; a shared-services spoke (DNS, AD, management) is reachable by everyone but cannot freely initiate into the rest.

The control plane is hub route tables plus labels. Each connection has two properties:

Every hub ships a Default and a None route table. Add your own for segmentation.

# A route table that production spokes associate with
az network vhub route-table create \
  --resource-group $RG \
  --vhub-name hub-weu \
  --name rt-prod \
  --labels prod

az network vhub route-table create \
  --resource-group $RG \
  --vhub-name hub-weu \
  --name rt-dev \
  --labels dev

To isolate prod from dev while both reach shared services: associate prod spokes with rt-prod propagating to prod and shared; associate dev spokes with rt-dev propagating to dev and shared; have the shared-services spoke propagate to all three. Because prod’s table never learns dev’s prefixes, segmentation is enforced in the route tables themselves — not in a firewall rule you might fat-finger.

Connection Associated table Propagates to labels Reachable from
Prod spokes rt-prod prod, shared Prod, shared
Dev spokes rt-dev dev, shared Dev, shared
Shared services Default prod, dev, shared Everyone
ExpressRoute / branches Default prod, dev, shared Everyone (tune as needed)

When routing intent is enabled, the platform injects the firewall next hop into these tables for you, so segmentation by route table and inspection by the firewall compose cleanly. Use static routes in a route table only for the exceptions — e.g. forcing a specific prefix to a third-party NVA the firewall does not front.

Step 5 - Integrating point-to-site and remote-user access at scale

Remote users land on the same backbone as everything else through a point-to-site (P2S) VPN gateway in the hub. Once connected, a user reaches any spoke or on-prem prefix the hub knows — global reach from a single client profile.

# P2S gateway sized by concurrent users; scale unit ~500 connections
az network p2s-vpn-gateway create \
  --resource-group $RG \
  --name p2sgw-weu \
  --location $LOCATION_EU \
  --vhub hub-weu \
  --scale-unit 2 \
  --vpn-server-config <vpn-server-config-id> \
  --address-space 10.200.0.0/24

Two things matter at enterprise scale:

  1. Authenticate against Entra ID (OpenVPN protocol), not certificates. A VPN server configuration with Entra ID auth gives you Conditional Access and MFA on the tunnel and kills certificate distribution. RADIUS and certificate auth exist but do not scale as cleanly for a global workforce.
  2. The P2S client address pool is just another prefix in the WAN — keep it globally unique and size it for peak concurrency. Users on the EU hub reach US spokes over the backbone, so you rarely need a gateway in every hub; place P2S gateways where your users physically are to minimize first-hop latency.

With routing intent on, remote-user traffic to private prefixes (and internet, if force-tunneled) is inspected by the hub firewall like any other flow — same policy, no special cases.

Step 6 - Observability, routing diagnostics, and cost control

The managed router is convenient until you need to know why a packet went somewhere. The tools that help:

az network vhub get-effective-routes \
  --resource-group $RG \
  --name hub-weu \
  --resource-type RouteTable \
  --resource-id <route-table-resource-id>
az network vpn-gateway list-bgp-peer-status \
  --resource-group $RG \
  --name vpngw-weu

On cost, Virtual WAN bills on axes hub-and-spoke does not:

Cost driver What to watch
Hub deployment + routing infra Per-hour charge per hub — consolidate regions; do not spin a hub per environment
Connection units Per-hour charge scaling with the number of connected VNets/branches
Data processed by the hub router Per-GB on traffic the hub routes — east-west across hubs adds up
Gateway scale units VPN/ER/P2S billed per scale unit whether or not saturated
Azure Firewall in hub Standard firewall pricing applies on top of the hub

The data-processing charge is the one that bites: chatty cross-region spoke-to-spoke traffic that was “free” intra-region peering is now metered per GB. Keep high-volume pairs in the same region’s hub, and reserve cross-hub transit for genuinely global flows.

Verify

Confirm the backbone behaves before you migrate production onto it.

# 1. Both hubs exist and are provisioned under one Virtual WAN
az network vhub list --resource-group $RG \
  --query "[].{name:name, state:provisioningState, addr:addressPrefix}" -o table

# 2. Routing intent is active on the secured hubs
az network vhub routing-intent list \
  --resource-group $RG --vhub-name hub-weu -o table

# 3. The firewall in the hub is healthy
az network firewall show \
  --resource-group $RG --name azfw-weu \
  --query "{name:name, state:provisioningState}" -o table

# 4. A spoke connection sees the firewall next hop and remote-region prefixes
az network vhub get-effective-routes \
  --resource-group $RG --name hub-weu \
  --resource-type RouteTable --resource-id <route-table-resource-id>

End-to-end functional checks:

Migration checklist: hub-spoke to Virtual WAN without downtime

The move is incremental — run both topologies side by side and cut spokes over one at a time.

Enterprise scenario

A retail group ran two secured hubs (hub-weu, hub-eus) with routing intent on both. Months after go-live, a new product team in West Europe complained their spoke could not reach a SaaS-style internal API hosted on a spoke behind East US. Connectivity to other EU spokes was fine, so it looked like a cross-region transit failure.

It was not. get-effective-routes on the EU spoke showed the East US prefix learned correctly, next-hop the local firewall — routing was healthy. The drops were in the East US Azure Firewall: when both InternetTraffic and PrivateTraffic route through the firewall, cross-hub east-west traffic is inspected in the destination hub too, and that hub’s policy had no rule permitting the EU spoke range.

The fix was a network rule in the shared parent policy (so both hubs inherit it), scoped to the actual prefixes rather than a blanket allow:

az network firewall policy rule-collection-group collection add-filter-collection \
  --resource-group $RG \
  --policy-name fwpol-global \
  --rule-collection-group-name rcg-eastwest \
  --name allow-weu-to-eus-api \
  --collection-priority 200 \
  --action Allow \
  --rule-name weu-spokes-to-api \
  --rule-type NetworkRule \
  --source-addresses 10.20.0.0/16 \
  --destination-addresses 10.30.4.0/24 \
  --destination-ports 443 \
  --ip-protocols TCP

The lesson the team wrote into their runbook: with routing intent, a packet crossing two hubs is inspected by two firewalls. Effective routes proving reachability is only half the diagnosis — always check the firewall logs in the destination hub before suspecting the backbone.

Going deeper

This section is for the reader who will own the backbone. It goes under the hood of the managed router, the connectivity types, and the levers you only reach for at scale.

Standard vs Basic: what the SKU actually gates

The Virtual WAN type (set with --type on the WAN, and mirrored by the hub --sku) decides which features exist at all:

Capability Basic Standard
Site-to-Site VPN Yes Yes
Hub-to-hub transit (any-to-any) No Yes
VNet-to-VNet transit through the hub No Yes
ExpressRoute connections No Yes
Point-to-Site (remote user) VPN No Yes
Azure Firewall + routing intent (secured hub) No Yes
Custom route tables No Yes

Basic is a Site-to-Site-VPN concentrator and nothing more — none of the transit, ExpressRoute, remote-user, or security features an enterprise backbone is built on. The SKU is a one-way ratchet: you can upgrade a Basic Virtual WAN to Standard in place, but you cannot downgrade Standard to Basic. Because no enterprise backbone ever wants the Basic feature set, provision Standard from the first az network vwan create and never think about it again.

The virtual hub is a managed router that auto-scales

A Virtual Hub is not just an address range — it is a Microsoft-operated router plus the gateways and firewall you attach. That router is what makes any-to-any transit work: every connection’s prefixes land in the hub’s route tables, and the router forwards between them without a peering or UDR from you.

The router’s throughput scales in routing infrastructure units. The floor is 2 units (roughly 3 Gbps and a few thousand spoke VMs); it scales toward 50 units (roughly 50 Gbps and tens of thousands of VMs) as you attach more connections. You do not manage instances — you size the gateways (in scale units) and let the hub router capacity follow the estate. The one thing you cannot change after creation is the hub address prefix, because the router, gateways, and firewall are all carved out of it. /23 is the safe default precisely so a future gateway always has room.

The four ways things connect to a hub

Everything that plugs into Virtual WAN is one of four connection types. Knowing which gateway each needs — and how each scales — is most of capacity planning:

Connection type What connects Gateway in the hub Scales in Typical auth
VNet connection A spoke VNet None (native hub connection) Hub router units N/A — a peering the platform owns
Site-to-Site (S2S) VPN Branch offices, SD-WAN, on-prem edge VPN gateway Scale units (~500 Mbps each) IPsec/IKE, optional BGP
Point-to-Site (P2S) VPN Individual remote users / laptops P2S VPN gateway Scale units (~500 connections each) Entra ID (OpenVPN), RADIUS, or certs
ExpressRoute Private circuits from on-prem / colo ExpressRoute gateway Scale units (~2 Gbps each) BGP over the circuit

A spoke (VNet connection) needs no gateway — that is why it is cheap and instant. The other three each require their gateway to exist in the hub first, which is why you deploy vpn-gateway, p2s-vpn-gateway, and express-route gateway before their sites, circuits, or clients can attach. All four, once connected, share the same any-to-any reachability: a P2S laptop on the EU hub can reach an ExpressRoute-attached datacenter behind the US hub with no extra configuration.

Secured hub: routing intent vs. the old way

There are two generations of “secure the hub with a firewall,” and you will meet both in real estates:

Routing intent does not replace custom route tables — it layers on top. You still use route tables and labels to decide who can reach whom (segmentation); routing intent decides what gets inspected (security). A prod spoke associated with rt-prod still only learns the prefixes you propagate to it, and separately its traffic to those prefixes is steered through the firewall. Segmentation and inspection are orthogonal — which is exactly why the combination is powerful.

Codified, routing intent is a first-class Terraform resource — no portal clicks, reviewable in a PR:

# Azure Firewall anchored to the hub, then routing intent pointing at it.
resource "azurerm_firewall" "weu" {
  name                = "azfw-weu"
  resource_group_name = azurerm_resource_group.vwan.name
  location            = "westeurope"
  sku_name            = "AZFW_Hub" # hub-anchored firewall
  sku_tier            = "Standard"
  firewall_policy_id  = azurerm_firewall_policy.global.id

  virtual_hub {
    virtual_hub_id  = azurerm_virtual_hub.weu.id
    public_ip_count = 1
  }
}

resource "azurerm_virtual_hub_routing_intent" "weu" {
  name           = "intent-weu"
  virtual_hub_id = azurerm_virtual_hub.weu.id

  routing_policy {
    name         = "InternetTrafficPolicy"
    destinations = ["Internet"]
    next_hop     = azurerm_firewall.weu.id
  }

  routing_policy {
    name         = "PrivateTrafficPolicy"
    destinations = ["PrivateTraffic"]
    next_hop     = azurerm_firewall.weu.id
  }
}

Pin your provider: azurerm v4 made subscription_id mandatory and turned several once-optional attributes into required ones. The vWAN resource names above are stable across v3 and v4, but a blind >= 3.0 constraint can still surprise you on terraform init.

Hub routing preference and BGP path selection

When the hub learns the same prefix from more than one place — say an on-prem 10.0.0.0/16 arriving over both ExpressRoute and a Site-to-Site VPN, or from a local branch and a remote hub — something has to break the tie. That lever is hub routing preference:

# Prefer ExpressRoute (default), the VPN gateway, or shortest AS path
az network vhub update \
  --resource-group $RG \
  --name hub-weu \
  --hub-routing-preference ExpressRoute   # or VpnGateway | ASPath

Getting this wrong is a classic asymmetric-routing trap: traffic leaves over ExpressRoute but returns over VPN because two hubs disagree on preference. Set it deliberately and identically where it matters.

Azure Route Server vs. the hub’s built-in router

Newcomers often ask where Azure Route Server (ARS) fits. ARS is a standalone service you drop into an ordinary VNet (in its own RouteServerSubnet) so a Network Virtual Appliance can exchange routes with the VNet over BGP instead of you maintaining UDRs — it is the dynamic-routing answer for classic hub-and-spoke. Virtual WAN does not need a separate Route Server: the virtual hub already contains that capability. When you must peer an NVA (a third-party SD-WAN or firewall appliance) with the hub, you create a BGP connection to the virtual hub (az network vhub bgpconnection create) rather than deploying ARS. Same BGP idea — but the route server is baked into the managed hub. Recognising they are one concept in two packagings ends a lot of “which one do I use?” churn.

Global reach, branch-to-branch, and ER-to-ER

Any-to-any is powerful enough that Microsoft gates some of it for safety:

Firewall Manager and where this sits in a landing zone

Azure Firewall Manager is the central plane for secured hubs: it converts hubs to secured, manages the firewall policy hierarchy (a parent policy with per-region child policies), and can configure routing intent across every hub from one place. In a mature estate, Virtual WAN lives in the connectivity subscription of your Azure Landing Zone, with Firewall Manager governing its security posture — see Azure landing zone: network topology and connectivity for where the backbone slots into the platform.

When NOT to use Virtual WAN

The honest tradeoff: Virtual WAN trades control and per-GB cost for operational simplicity and global scale. It is the right call for multi-region, many-branch, ExpressRoute-plus-VPN estates. It is overkill — and pricier — for:

Practice challenges

Work these in a throwaway resource group. Nothing here needs production data; every command is real az CLI (schema shown, not run here — swap in your own IDs and prefixes). Try each before opening the solution.

Challenge 1 (beginner) — Stand up a Standard WAN and one hub

Create a Standard Virtual WAN and a single virtual hub in West Europe with a correctly sized address prefix that overlaps nothing.

<details> <summary>Solution</summary>

az group create --name rg-vwan-lab --location westeurope

az network vwan create \
  --resource-group rg-vwan-lab \
  --name vwan-lab \
  --location westeurope \
  --type Standard

az network vhub create \
  --resource-group rg-vwan-lab \
  --vwan vwan-lab \
  --name hub-weu \
  --location westeurope \
  --address-prefix 10.100.0.0/23 \
  --sku Standard

Why: --type Standard (not Basic) unlocks transit, ExpressRoute, P2S, and secured-hub features, and the /23 gives the hub room for the gateways and firewall you cannot add later if you undersize it. The prefix is chosen to overlap no spoke, branch, or on-prem range. </details>

Challenge 2 (beginner) — Attach a spoke and observe any-to-any

Attach an existing spoke VNet to the hub as a hub virtual network connection, without peering or UDRs, and predict what it can now reach.

<details> <summary>Solution</summary>

SPOKE_ID=$(az network vnet show -g rg-apps-weu -n vnet-app-weu --query id -o tsv)

az network vhub connection create \
  --resource-group rg-vwan-lab \
  --vhub-name hub-weu \
  --name conn-app-weu \
  --remote-vnet $SPOKE_ID

Why: A single connection gives the spoke transit to every other spoke on this hub and, once a second hub exists, to spokes behind it across the Microsoft backbone — with zero peerings or route tables authored by you. That default reachability is exactly what you will later have to restrict with route tables (Challenge 5). </details>

Challenge 3 (intermediate) — Add a VPN gateway and model a branch

Deploy a Site-to-Site VPN gateway into the hub and model one branch office as a VPN site so an IPsec tunnel can form.

<details> <summary>Solution</summary>

# 1. VPN gateway in the hub (2 scale units ~ 1 Gbps aggregate)
az network vpn-gateway create \
  --resource-group rg-vwan-lab \
  --name vpngw-weu \
  --location westeurope \
  --vhub hub-weu \
  --vpn-gateway-scale-unit 2

# 2. The branch, as a VPN site (public IP + on-prem prefixes or BGP ASN)
az network vpn-site create \
  --resource-group rg-vwan-lab \
  --name site-london \
  --location westeurope \
  --virtual-wan vwan-lab \
  --ip-address 203.0.113.10 \
  --address-prefixes 192.168.10.0/24

Why: Branches never attach directly to a hub — they attach to the hub’s VPN gateway, and each physical site is modelled as a VPN site (its public IP and the prefixes behind it). 203.0.113.10 and 192.168.10.0/24 are documentation ranges standing in for a real branch edge and its LAN. At scale, an SD-WAN partner’s controller creates these sites for you instead of you hand-running this command. </details>

Challenge 4 (intermediate) — Send everything through the firewall with routing intent

Azure Firewall is already deployed in the hub. Turn the hub into a secured hub so both internet and private traffic are inspected — without writing a single per-spoke UDR.

<details> <summary>Solution</summary>

FW_ID=$(az network firewall show -g rg-vwan-lab -n azfw-weu --query id -o tsv)

# routing-policies.json:
# [
#   { "name": "InternetTrafficPolicy", "destinations": ["Internet"],       "nextHop": "<FW_ID>" },
#   { "name": "PrivateTrafficPolicy",  "destinations": ["PrivateTraffic"], "nextHop": "<FW_ID>" }
# ]

az network vhub routing-intent create \
  --resource-group rg-vwan-lab \
  --vhub hub-weu \
  --name intent-weu \
  --routing-policies @routing-policies.json

Why: Routing intent programs every route table in the hub to send 0.0.0.0/0 and the RFC1918 ranges to the firewall’s private IP. Two declarative policies replace what used to be hundreds of UDRs, and — critically — the injected next hop shows up in get-effective-routes, so it is inspectable, not magic. Enable it on every secured hub; it is per-hub. </details>

Challenge 5 (advanced) — Segment prod from dev with route tables and labels

Both prod and dev spokes must reach a shared-services spoke (DNS/AD), but prod and dev must not reach each other. Design and create the route tables.

<details> <summary>Solution</summary>

az network vhub route-table create \
  --resource-group rg-vwan-lab --vhub-name hub-weu \
  --name rt-prod --labels prod

az network vhub route-table create \
  --resource-group rg-vwan-lab --vhub-name hub-weu \
  --name rt-dev --labels dev

Associations and propagation (set on each connection):

Connection Associated table Propagates to labels
Prod spokes rt-prod prod, shared
Dev spokes rt-dev dev, shared
Shared services Default prod, dev, shared

Why: A connection’s associated table decides where its traffic can go; its propagated labels decide who learns its prefixes. Because rt-prod never learns dev’s prefixes (dev only propagates to dev and shared), prod literally has no route to dev — segmentation enforced in the route tables, not in a firewall rule you could fat-finger. Shared services propagate everywhere, so both environments reach them. </details>

Challenge 6 (advanced) — Codify it and wire a deployment pipeline

Portal-built hubs drift. Express the WAN, hub, and routing intent in Terraform, then add a CI job that plans and applies on merge using OIDC (no stored secrets).

<details> <summary>Solution</summary>

resource "azurerm_virtual_wan" "lab" {
  name                = "vwan-lab"
  resource_group_name = azurerm_resource_group.vwan.name
  location            = "westeurope"
  type                = "Standard"
}

resource "azurerm_virtual_hub" "weu" {
  name                = "hub-weu"
  resource_group_name = azurerm_resource_group.vwan.name
  location            = "westeurope"
  virtual_wan_id      = azurerm_virtual_wan.lab.id
  address_prefix      = "10.100.0.0/23"
}
# (azurerm_firewall + azurerm_virtual_hub_routing_intent as shown in "Going deeper")
name: vwan-deploy
on:
  push:
    branches: [main]
    paths: ["vwan/**"]
permissions:
  id-token: write # OIDC federated login — no client secret stored
  contents: read
jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init && terraform apply -auto-approve
        working-directory: vwan

Why: The topology — hubs, route tables, labels, routing intent — becomes reviewable in a pull request instead of living as click-ops nobody can reproduce. The pipeline authenticates with a federated (OIDC) credential (id-token: write + azure/login@v2), so there is no long-lived client secret in GitHub. This is the “codify it” next step the closing section points to. </details>

Common beginner mistakes

These are the misconceptions that trip people new to Virtual WAN — distinct from the architect-level pitfalls in the next section. Each is the wrong mental model, then the right one.

Pitfalls and next steps

The mistakes that cause re-architecture, not just tickets:

From here, codify the topology in Terraform or Bicep so hubs, route tables, labels, and routing intent are reviewed and reproducible rather than portal artifacts; structure firewall policy into a parent/child hierarchy for per-region overrides; and fold the Virtual WAN into your Azure Landing Zone connectivity subscription so it becomes the sanctioned global network, not a one-off.

Glossary

AzureVirtual WANNetworkingLanding ZoneRoutingHub-Spoke
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments