Challenge 06: VNet peering & gateway transit
90-120 minutes | ~$1.50/hour (VPN Gateway is the major cost driver) | Exam weight: 20-25%
Scenario
Contoso has a hub-spoke network where the hub VNet contains a VPN Gateway connecting to on-premises. Spoke VNets need to access on-premises resources through the hub's VPN Gateway (gateway transit). Additionally, some spokes need to communicate with each other via the hub (service chaining through an NVA), since VNet peering is non-transitive by default.
Learning objectives
After completing this challenge you will be able to:
- Create a hub-spoke topology with VNet peering
- Configure gateway transit so spoke VNets use the hub's VPN Gateway
- Explain and demonstrate the non-transitive nature of VNet peering
- Implement service chaining with user-defined routes (UDRs) and a network virtual appliance (NVA)
- Configure global VNet peering across regions
- Verify peering status, effective routes, and end-to-end connectivity
Prerequisites
- An Azure subscription with Contributor access
- Azure CLI installed and authenticated (
az login) - A resource group for this lab (or permission to create one)
- Basic understanding of IP routing and address spaces
Task 1: Create the hub-spoke topology with VNet peering
Build a hub VNet and two spoke VNets, then establish peering connections between hub and each spoke.
Step 1: Create the resource group
az group create \
--name rg-peering-lab \
--location eastus2
Step 2: Create the hub VNet
az network vnet create \
--resource-group rg-peering-lab \
--name vnet-hub \
--location eastus2 \
--address-prefixes 10.0.0.0/16 \
--subnet-name GatewaySubnet \
--subnet-prefixes 10.0.0.0/27
Add a subnet for the NVA:
az network vnet subnet create \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name subnet-nva \
--address-prefixes 10.0.1.0/24
Add a workload subnet in the hub:
az network vnet subnet create \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name subnet-hub-workload \
--address-prefixes 10.0.2.0/24
Step 3: Create spoke VNets
az network vnet create \
--resource-group rg-peering-lab \
--name vnet-spoke1 \
--location eastus2 \
--address-prefixes 10.1.0.0/16 \
--subnet-name subnet-workload \
--subnet-prefixes 10.1.1.0/24
az network vnet create \
--resource-group rg-peering-lab \
--name vnet-spoke2 \
--location eastus2 \
--address-prefixes 10.2.0.0/16 \
--subnet-name subnet-workload \
--subnet-prefixes 10.2.1.0/24
Step 4: Create peering from hub to spoke1
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke1 \
--remote-vnet vnet-spoke1 \
--allow-vnet-access true \
--allow-forwarded-traffic true
Step 5: Create peering from spoke1 to hub
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke1 \
--name spoke1-to-hub \
--remote-vnet vnet-hub \
--allow-vnet-access true \
--allow-forwarded-traffic true
Step 6: Create peering between hub and spoke2
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke2 \
--remote-vnet vnet-spoke2 \
--allow-vnet-access true \
--allow-forwarded-traffic true
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke2 \
--name spoke2-to-hub \
--remote-vnet vnet-hub \
--allow-vnet-access true \
--allow-forwarded-traffic true
Step 7: Verify peering status
Both sides must show Connected for traffic to flow:
az network vnet peering list \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--output table
az network vnet peering show \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke1 \
--query peeringState \
--output tsv
Peering must be created on both sides. If only one side is created, the state is Initiated on that side and Disconnected on the other. Traffic does not flow until both sides reach Connected state.
Task 2: Configure gateway transit
Configure the hub VPN Gateway and enable gateway transit so spoke VNets can reach on-premises networks through the hub's gateway.
Step 1: Create a public IP for the VPN Gateway
az network public-ip create \
--resource-group rg-peering-lab \
--name pip-vpn-gateway \
--allocation-method Static \
--sku Standard \
--location eastus2
Step 2: Create the VPN Gateway (takes 30-45 minutes)
az network vnet-gateway create \
--resource-group rg-peering-lab \
--name vpngw-hub \
--vnet vnet-hub \
--gateway-type Vpn \
--vpn-type RouteBased \
--sku VpnGw1 \
--public-ip-addresses pip-vpn-gateway \
--no-wait
The --no-wait flag returns immediately. Check provisioning status with:
az network vnet-gateway show \
--resource-group rg-peering-lab \
--name vpngw-hub \
--query provisioningState \
--output tsv
Wait until the output shows Succeeded before proceeding.
Step 3: Update hub-to-spoke peering to allow gateway transit
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke1 \
--set allowGatewayTransit=true
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke2 \
--set allowGatewayTransit=true
Step 4: Update spoke-to-hub peering to use remote gateways
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke1 \
--name spoke1-to-hub \
--set useRemoteGateways=true
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke2 \
--name spoke2-to-hub \
--set useRemoteGateways=true
The useRemoteGateways setting will fail if the hub VNet does not have a gateway deployed and in Succeeded provisioning state. You must wait for the VPN Gateway creation to complete before setting this flag on the spoke peering.
Step 5: Verify gateway transit configuration
az network vnet peering show \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke1 \
--query '{allowGatewayTransit:allowGatewayTransit, peeringState:peeringState}' \
--output json
az network vnet peering show \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke1 \
--name spoke1-to-hub \
--query '{useRemoteGateways:useRemoteGateways, peeringState:peeringState}' \
--output json
Gateway transit allows spoke VNets to use the hub gateway as if it were their own. The hub side sets allowGatewayTransit=true and each spoke sets useRemoteGateways=true. A VNet cannot use remote gateways if it already has its own gateway deployed. Global peering supports gateway transit only with VpnGw1 or higher gateways (not Basic SKU).
Task 3: Demonstrate non-transitivity of VNet peering
Peering is non-transitive: even though Spoke1 peers with Hub and Hub peers with Spoke2, Spoke1 cannot automatically reach Spoke2. This task demonstrates this behavior by deploying VMs and checking effective routes.
Step 1: Deploy a test VM in spoke1
az vm create \
--resource-group rg-peering-lab \
--name vm-spoke1 \
--vnet-name vnet-spoke1 \
--subnet subnet-workload \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys \
--no-wait
Step 2: Deploy a test VM in spoke2
az vm create \
--resource-group rg-peering-lab \
--name vm-spoke2 \
--vnet-name vnet-spoke2 \
--subnet subnet-workload \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys \
--no-wait
Step 3: Check effective routes on the spoke1 VM NIC
Once the VM is provisioned, retrieve the effective routes to verify what destinations the spoke1 VM can reach:
az network nic show-effective-route-table \
--resource-group rg-peering-lab \
--name vm-spoke1VMNic \
--output table
You will see routes for:
10.1.0.0/16(local VNet) with next hopVnetLocal10.0.0.0/16(hub VNet via peering) with next hopVNetPeering
You will NOT see a route for 10.2.0.0/16 (spoke2). This confirms non-transitivity: spoke1 can reach the hub, but not spoke2 through the hub.
Step 4: Attempt connectivity from spoke1 to spoke2
az network watcher test-connectivity \
--resource-group rg-peering-lab \
--source-resource vm-spoke1 \
--dest-address 10.2.1.4 \
--dest-port 22
This test should return ConnectionStatus: Unreachable because there is no direct peering or route between the two spokes.
VNet peering is always non-transitive. If VNet A peers with VNet B and VNet B peers with VNet C, VNet A has no path to VNet C unless you either (a) peer A directly with C, or (b) route traffic through an NVA or Azure Firewall in VNet B using UDRs (service chaining).
Task 4: Implement service chaining with UDRs
Enable spoke-to-spoke communication by routing traffic through a network virtual appliance (NVA) in the hub VNet.
Step 1: Deploy an NVA in the hub
az vm create \
--resource-group rg-peering-lab \
--name vm-nva \
--vnet-name vnet-hub \
--subnet subnet-nva \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys \
--private-ip-address 10.0.1.4
Step 2: Enable IP forwarding on the NVA NIC
The NVA must forward packets that are not destined for itself. Enable IP forwarding at the Azure networking layer:
az network nic update \
--resource-group rg-peering-lab \
--name vm-nvaVMNic \
--ip-forwarding true
Also enable IP forwarding inside the Linux VM:
az vm run-command invoke \
--resource-group rg-peering-lab \
--name vm-nva \
--command-id RunShellScript \
--scripts "sudo sysctl -w net.ipv4.ip_forward=1 && echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf"
Step 3: Create a route table for spoke1
az network route-table create \
--resource-group rg-peering-lab \
--name rt-spoke1 \
--location eastus2
Add a route directing spoke2 traffic to the NVA:
az network route-table route create \
--resource-group rg-peering-lab \
--route-table-name rt-spoke1 \
--name to-spoke2 \
--address-prefix 10.2.0.0/16 \
--next-hop-type VirtualAppliance \
--next-hop-ip-address 10.0.1.4
Step 4: Create a route table for spoke2
az network route-table create \
--resource-group rg-peering-lab \
--name rt-spoke2 \
--location eastus2
az network route-table route create \
--resource-group rg-peering-lab \
--route-table-name rt-spoke2 \
--name to-spoke1 \
--address-prefix 10.1.0.0/16 \
--next-hop-type VirtualAppliance \
--next-hop-ip-address 10.0.1.4
Step 5: Associate route tables with spoke subnets
az network vnet subnet update \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke1 \
--name subnet-workload \
--route-table rt-spoke1
az network vnet subnet update \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke2 \
--name subnet-workload \
--route-table rt-spoke2
Step 6: Verify effective routes now include the UDR
az network nic show-effective-route-table \
--resource-group rg-peering-lab \
--name vm-spoke1VMNic \
--output table
You should now see a route for 10.2.0.0/16 with next hop type VirtualAppliance and next hop address 10.0.1.4.
Step 7: Test spoke-to-spoke connectivity
az network watcher test-connectivity \
--resource-group rg-peering-lab \
--source-resource vm-spoke1 \
--dest-address 10.2.1.4 \
--dest-port 22
The connection status should now show Reachable (assuming NSGs allow the traffic and the NVA is forwarding packets).
For service chaining to work, --allow-forwarded-traffic must be set to true on BOTH sides of EACH peering connection. Traffic from spoke1 destined for spoke2 enters the hub as forwarded traffic (since the hub is not the original destination). If allowForwardedTraffic is false on the hub-to-spoke2 peering, the hub will not forward that traffic onward to spoke2.
Task 5: Configure global VNet peering
Create a VNet in a different region and establish global (cross-region) peering with the hub.
Step 1: Create a VNet in a second region
az network vnet create \
--resource-group rg-peering-lab \
--name vnet-spoke3-westeurope \
--location westeurope \
--address-prefixes 10.3.0.0/16 \
--subnet-name subnet-workload \
--subnet-prefixes 10.3.1.0/24
Step 2: Create global peering from hub to spoke3
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke3 \
--remote-vnet vnet-spoke3-westeurope \
--allow-vnet-access true \
--allow-forwarded-traffic true \
--allow-gateway-transit true
Step 3: Create global peering from spoke3 to hub
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke3-westeurope \
--name spoke3-to-hub \
--remote-vnet vnet-hub \
--allow-vnet-access true \
--allow-forwarded-traffic true \
--use-remote-gateways true
Step 4: Verify global peering status
az network vnet peering show \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke3 \
--query '{peeringState:peeringState, allowGatewayTransit:allowGatewayTransit, remoteVnetRegion:remoteVirtualNetwork.id}' \
--output json
Step 5: Deploy a VM and test latency
az vm create \
--resource-group rg-peering-lab \
--name vm-spoke3 \
--vnet-name vnet-spoke3-westeurope \
--subnet subnet-workload \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys \
--location westeurope \
--no-wait

### Step 2: Check effective routes from the hub NVA perspective
```bash
az network nic show-effective-route-table \
--resource-group rg-peering-lab \
--name vm-nvaVMNic \
--output table
The NVA should see routes to all peered VNets (10.1.0.0/16, 10.2.0.0/16, 10.3.0.0/16) with next hop type VNetPeering or VNetGlobalPeering.
Step 3: Verify spoke1 has routes to on-premises via gateway transit
If the VPN Gateway has learned on-premises routes (for example, 192.168.0.0/16 via BGP), check that spoke1 inherits them:
az network nic show-effective-route-table \
--resource-group rg-peering-lab \
--name vm-spoke1VMNic \
--query "[?source=='VirtualNetworkGateway']" \
--output table
Routes learned from the gateway will appear with source VirtualNetworkGateway because useRemoteGateways=true causes the spoke to inherit the hub gateway's route table.
Step 4: Run a full connectivity check
# Spoke1 to Hub NVA (should succeed - direct peering)
az network watcher test-connectivity \
--resource-group rg-peering-lab \
--source-resource vm-spoke1 \
--dest-address 10.0.1.4 \
--dest-port 22
# Spoke1 to Spoke2 (should succeed - via NVA service chaining)
az network watcher test-connectivity \
--resource-group rg-peering-lab \
--source-resource vm-spoke1 \
--dest-address 10.2.1.4 \
--dest-port 22
Break & fix
Scenario 1: Peering stuck in "Initiated" state
A colleague created peering from the hub to a new spoke VNet, but traffic is not flowing. You check the peering state:
az network vnet peering show \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke1 \
--query peeringState \
--output tsv
Output: Initiated
Root cause: Peering was only created on the hub side. The reverse peering (spoke to hub) was never created.
Fix: Create the missing peering on the spoke side:
az network vnet peering create \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke1 \
--name spoke1-to-hub \
--remote-vnet vnet-hub \
--allow-vnet-access true \
--allow-forwarded-traffic true
After both sides exist, the state transitions to Connected on both peerings.
Scenario 2: Gateway transit fails on spoke peering
You attempt to enable useRemoteGateways on a spoke peering but receive an error:
"Cannot use remote gateways because the referenced virtual network has no gateways"
Root cause: The VPN Gateway in the hub VNet is either not yet deployed or still in provisioning state.
Fix: Verify the gateway exists and is fully provisioned:
az network vnet-gateway show \
--resource-group rg-peering-lab \
--name vpngw-hub \
--query provisioningState \
--output tsv
Wait until it shows Succeeded, then retry enabling useRemoteGateways. If the gateway does not exist at all, deploy it first (see Task 2, Step 2).
Scenario 3: Spoke-to-spoke traffic blocked despite UDRs
Route tables are correctly configured pointing to the NVA, and the NVA has IP forwarding enabled. However, traffic from spoke1 to spoke2 still fails.
Root cause: The hub-to-spoke2 peering has allowForwardedTraffic set to false. The hub receives the packet from spoke1 and routes it to the NVA, but when the NVA forwards the packet toward spoke2, the peering drops it because forwarded traffic is not permitted.
Fix: Update the peering to allow forwarded traffic:
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-hub \
--name hub-to-spoke2 \
--set allowForwardedTraffic=true
Also verify the spoke2-to-hub peering allows forwarded traffic (needed for return traffic):
az network vnet peering update \
--resource-group rg-peering-lab \
--vnet-name vnet-spoke2 \
--name spoke2-to-hub \
--set allowForwardedTraffic=true
Clean up resources
Delete the resource group to remove all lab resources and stop incurring charges (especially the VPN Gateway):
az group delete \
--name rg-peering-lab \
--yes \
--no-wait
Knowledge check
1. You create a VNet peering from VNet-A to VNet-B but forget to create the reverse peering. What state will the peering show on VNet-A?
2. Contoso has a hub-spoke topology. Spoke1 peers with Hub and Hub peers with Spoke2. A VM in Spoke1 tries to reach a VM in Spoke2. What happens?
3. You want spoke VNets to use the hub VPN Gateway to reach on-premises. Which combination of settings is correct?
4. Service chaining through an NVA in a hub requires which setting on the hub-to-spoke peering?
5. You attempt to set useRemoteGateways=true on a spoke peering but receive an error. What is the most likely cause?
6. Which VPN Gateway SKU does NOT support gateway transit over global VNet peering?