Home / Azure

AzureTroubleshootingIntermediate

Azure VNet peering is Connected but traffic does not flow: states, routes, NSGs and transit

Peered VMs can't talk, or spokes can't reach each other. Check peering state and sync, the access flags, effective routes and NSGs, then apply the matching fix.

Deepak Bhardwaj7 min read

Applies toAzure Virtual Network peering (regional and global), Azure CLI. Output values can vary by CLI version.

A connection between two peered VNets times out, even though the portal may show the peering as healthy. Connected only means both links exist. It doesn't prove that the access flags, routes, NSGs or name resolution are right.

Symptom, cause and fix

Symptom Likely cause Fix
Peering state is Initiated Only one link has been created Create the link from the other VNet
Peering state is Disconnected One link was deleted, often after a move or address space change Delete the remaining links, then recreate both
New address range is unreachable; peeringSyncLevel isn't FullyInSync Address space changed and the peering wasn't synced az network vnet peering sync
State is Connected, all traffic fails allowVirtualNetworkAccess is false on a side, or an NSG denies it Enable access; fix the NSG rule
Traffic goes to a firewall or is dropped A UDR overrides the peering route Fix the route or the firewall rule
Spoke can't reach another spoke Peering isn't transitive Route through an NVA or Azure Firewall in the hub
Spoke can't reach on-premises Gateway transit flags missing, or on-premises has no route back Set the flags; check the advertised routes
IP works, hostname fails Name resolution doesn't cross the peering by itself Link the private DNS zone or fix custom DNS

Check 1: state and sync on both sides

Every peering has two links, so look at both VNets. They may be in different subscriptions.

az network vnet peering list -g rg-hub --vnet-name vnet-hub \
  --query "[].{name:name, state:peeringState, sync:peeringSyncLevel, access:allowVirtualNetworkAccess, forwarded:allowForwardedTraffic, gwTransit:allowGatewayTransit, remoteGw:useRemoteGateways}" \
  -o table

Repeat on the other VNet. Healthy means Connected, FullyInSync and access True on both sides.

Next, compare the ranges the peering knows about with the remote VNet's real ranges:

az network vnet peering show -g rg-hub --vnet-name vnet-hub -n hub-to-spoke1 \
  --query "remoteVirtualNetworkAddressSpace.addressPrefixes"

az network vnet show -g rg-spoke1 -n vnet-spoke1 \
  --query "addressSpace.addressPrefixes"

Check 2: effective routes on the source NIC

The VM must be running for this to work.

az network nic show-effective-route-table -g rg-spoke1 -n vm-app1-nic -o table

Find the destination prefix. You should see an active route with a next hop type such as VNetPeering or VNetGlobalPeering (names vary by version). Then confirm what the platform does with one flow:

az network watcher show-next-hop -g rg-spoke1 --vm vm-app1 \
  --source-ip 10.1.0.4 --dest-ip 10.2.0.4
  • VirtualAppliance means a UDR sends the flow to a firewall or NVA. Continue the investigation there.
  • None means Azure drops the packet.
  • No route to the prefix on a peered VNet points back to Check 1: sync, state or the wrong address range.

Check 3: NSGs on both ends

Test the flow from each VM's point of view:

# Outbound from the source
az network watcher test-ip-flow -g rg-spoke1 --vm vm-app1 \
  --direction Outbound --protocol TCP \
  --local 10.1.0.4:49152 --remote 10.2.0.4:443

# Inbound at the destination
az network watcher test-ip-flow -g rg-spoke2 --vm vm-db1 \
  --direction Inbound --protocol TCP \
  --local 10.2.0.4:443 --remote 10.1.0.4:49152

The output names the rule that allowed or denied the flow.

Peered ranges are part of the VirtualNetwork service tag, so the default rules allow this traffic. Failures usually come from a custom deny rule, or an allow rule written for a specific CIDR that doesn't include a new range.

If routes and NSGs are clean, check the destination guest: OS firewall, and whether the service is listening on that port.

Fixes

Peering is Initiated or Disconnected

Initiated needs the missing link. --remote-vnet needs the full resource ID when the VNets are in different subscriptions.

az network vnet peering create -g rg-spoke1 --vnet-name vnet-spoke1 -n spoke1-to-hub \
  --remote-vnet /subscriptions/<sub-id>/resourceGroups/rg-hub/providers/Microsoft.Network/virtualNetworks/vnet-hub \
  --allow-vnet-access true --allow-forwarded-traffic true

Disconnected can't be repaired in place. Microsoft's guidance is to delete the links on both VNets and create them again.

Caution

Deleting a peering stops all traffic across it, including any gateway transit the spoke relies on. Save each side's flags from Check 1 first, recreate them exactly and do the work in a change window if any traffic still flows.

az network vnet peering delete -g rg-hub --vnet-name vnet-hub -n hub-to-spoke1
az network vnet peering delete -g rg-spoke1 --vnet-name vnet-spoke1 -n spoke1-to-hub
# Then run az network vnet peering create from each VNet.

Peering is out of sync

az network vnet peering sync -g rg-hub --vnet-name vnet-hub -n hub-to-spoke1

Run it for each link that isn't FullyInSync, then re-run Check 1 on both sides. The account needs rights on both VNets. Microsoft recommends a sync after every address space resize, rather than batching resizes.

Access flag is off

az network vnet peering update -g rg-spoke1 --vnet-name vnet-spoke1 -n spoke1-to-hub \
  --set allowVirtualNetworkAccess=true

Spoke to spoke

Peering isn't transitive. If spoke1 and spoke2 both peer with the hub, they still can't reach each other through it without a forwarding device or a direct peering. To route through the hub:

  1. Add a UDR on each spoke subnet that sends the other spoke's range to the firewall or NVA private IP.
  2. Enable forwarded traffic on the links between the hub and each spoke.
  3. Allow the flow in the firewall or NVA.
  4. For an NVA VM, enable IP forwarding on its NIC and inside its OS.
az network vnet peering update -g rg-hub --vnet-name vnet-hub -n hub-to-spoke1 \
  --set allowForwardedTraffic=true
az network vnet peering update -g rg-spoke1 --vnet-name vnet-spoke1 -n spoke1-to-hub \
  --set allowForwardedTraffic=true

az network route-table route create -g rg-spoke1 --route-table-name rt-spoke1 \
  -n to-spoke2 --address-prefix 10.2.0.0/16 \
  --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4

# NVA VMs only. Azure Firewall doesn't need this.
az network nic update -g rg-hub -n nva1-nic --ip-forwarding true

Make the routes symmetric, or a stateful firewall will drop the return traffic.

Spoke to on-premises through the hub gateway

The hub link needs allowGatewayTransit and the spoke link needs useRemoteGateways.

az network vnet peering update -g rg-hub --vnet-name vnet-hub -n hub-to-spoke1 \
  --set allowGatewayTransit=true
az network vnet peering update -g rg-spoke1 --vnet-name vnet-spoke1 -n spoke1-to-hub \
  --set useRemoteGateways=true

A VNet that already has its own gateway can't use a remote one, and only one of its peerings can have the flag set. For cross-tenant peerings, use the CLI or PowerShell rather than the portal.

If the flags are right, check that on-premises can route back. With BGP, confirm the gateway advertises the spoke range:

az network vnet-gateway list-advertised-routes -g rg-hub -n gw-hub --peer 10.255.0.1 -o table

Name resolution fails

A private DNS zone only answers for VNets linked to it. Peering doesn't extend that link, or Azure-provided VM name resolution, to the remote VNet.

az network private-dns link vnet create -g rg-dns -z privatelink.blob.core.windows.net \
  -n link-spoke1 --virtual-network <spoke1-vnet-resource-id> --registration-enabled false

Overlapping address spaces can't be fixed with a setting: Azure rejects peering between overlapping ranges, so one VNet has to be readdressed.

Prevent it next time

  • Run a sync as part of every address space change, and put it in the same pipeline step.
  • Define both links in the same Bicep or Terraform module, so one side can't be changed or deleted alone.
  • Write NSG rules with the VirtualNetwork tag or include future ranges in your IP plan.
  • Audit peering health on a schedule with Azure Resource Graph (needs the resource-graph CLI extension):
az graph query -q "
resources
| where type =~ 'microsoft.network/virtualnetworks'
| mv-expand peering = properties.virtualNetworkPeerings
| extend state = tostring(peering.properties.peeringState),
         sync = tostring(peering.properties.peeringSyncLevel)
| where state != 'Connected' or sync != 'FullyInSync'
| project vnet = name, resourceGroup, peering = tostring(peering.name), state, sync"

Investigate any row, including a blank sync value.