Traffic Flow Through Azure Firewall via UDR
The UDR (User Defined Route) is the mechanism that forces all spoke traffic through Azure Firewall — overriding Azure’s default system routes which would otherwise send traffic directly between peered VNets, bypassing inspection entirely.

Why UDRs Are Necessary
Azure VNet peering by default creates direct routing between peered VNets — packets travel peer-to-peer without touching any intermediate device. This means without UDRs, a VM in Spoke 1 talking to a VM in Spoke 2 completely bypasses Azure Firewall:
Default behaviour (NO UDR): Spoke 1 VM (10.1.1.4) → Spoke 2 VM (10.2.1.4) System route: 10.2.0.0/16 → VNet peering (direct) Result: traffic bypasses firewall entirely ❌With UDR applied to spoke subnet: Spoke 1 VM (10.1.1.4) → Spoke 2 VM (10.2.1.4) UDR overrides: 0.0.0.0/0 → 10.0.1.4 (Firewall private IP) Result: traffic hits firewall → inspected → forwarded ✅
The UDR wins because custom routes always override system routes — Azure’s route selection priority is custom UDR first, then BGP routes, then system routes.
Route Table Structure
A route table is an Azure resource associated with one or more subnets. Every subnet that needs inspection gets the same core UDR:
Route Table: rt-spoke1-subnetsAssociated to: Spoke 1 subnet A, Spoke 1 subnet BRoutes: Name Prefix Next hop type Next hop IP ───────────────────────────────────────────────────────────────── force-to-fw 0.0.0.0/0 Virtual Appliance 10.0.1.4
Deployed via ARM / Bicep:
resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = { name: 'rt-spoke1-subnets' location: location properties: { disableBgpRoutePropagation: true // ← critical — explained below routes: [ { name: 'force-to-firewall' properties: { addressPrefix: '0.0.0.0/0' nextHopType: 'VirtualAppliance' nextHopIpAddress: '10.0.1.4' // Azure Firewall private IP } } ] }}// Associate with spoke subnetresource subnetAssociation 'Microsoft.Network/virtualNetworks/subnets@2023-04-01' = { name: 'spoke1/snet-app' properties: { addressPrefix: '10.1.1.0/24' routeTable: { id: routeTable.id } }}
The Three Traffic Paths
Path 1 — North-South Outbound (spoke VM → internet)
Step 1: Spoke 1 VM (10.1.1.4) sends packet to 8.8.8.8Step 2: Subnet route table consulted UDR match: 0.0.0.0/0 → next hop 10.0.1.4 (Firewall)Step 3: Packet arrives at Azure Firewall private IPStep 4: Firewall evaluates application rules Rule: allow src=10.1.0.0/16 dest=*.google.com proto=HTTPS → ALLOWStep 5: Firewall SNATs packet Source IP changed: 10.1.1.4 → Firewall public IP (20.x.x.x)Step 6: Packet exits to internet from Firewall public IP Return traffic arrives at Firewall public IPStep 7: Firewall translates back → forwards to 10.1.1.4
SNAT is automatic for internet-bound traffic — the spoke VM’s private IP is never exposed to the internet. Azure Firewall’s public IP is the only address the internet sees.
Path 2 — North-South Inbound (internet → spoke VM)
Step 1: External client sends to Firewall public IP 20.x.x.x:443Step 2: Firewall DNAT rule fires: dest 20.x.x.x:443 → translated to 10.1.4.5:443 (spoke VM)Step 3: Firewall forwards to 10.1.4.5 via VNet peering pathStep 4: Packet arrives at spoke VM — no public IP needed on VMStep 5: VM responds to Firewall private IP (it sees FW as source) UDR on VM subnet ensures return goes back through FirewallStep 6: Firewall forwards return to external client
Path 3 — East-West (spoke 1 VM → spoke 2 VM)
This is the most important path for security — lateral movement between spokes must be inspected:
Step 1: Spoke 1 VM (10.1.1.4) sends packet to Spoke 2 VM (10.2.1.8)Step 2: Spoke 1 subnet route table consulted UDR: 0.0.0.0/0 → 10.0.1.4 (matches — more specific than system route)Step 3: Packet arrives at Azure FirewallStep 4: Firewall evaluates network rules Rule: allow src=10.1.0.0/16 dest=10.2.1.8 port=443 → ALLOW (or deny if no rule matches)Step 5: Firewall forwards to 10.2.1.8 via peering to Spoke 2Step 6: Spoke 2 subnet route table: UDR: 0.0.0.0/0 → 10.0.1.4 Return traffic: 10.2.1.8 → 10.1.1.4 UDR forces return through Firewall tooStep 7: Firewall forwards return packet to Spoke 1 VM
Both directions of every connection traverse the Firewall — request and response. This is essential for stateful inspection — if only one direction went through the Firewall, the session state table would be incomplete.
disableBgpRoutePropagation — Why It Matters
Every route table has a disableBgpRoutePropagation flag. On spoke subnets this must be set to true:
disableBgpRoutePropagation: false (default) → VPN Gateway pushes on-premises routes into spoke effective routes → Spoke VM sends on-premises traffic directly to Gateway → Bypasses Firewall for on-premises bound traffic ❌disableBgpRoutePropagation: true (required for spoke subnets) → VPN Gateway routes suppressed on spoke subnets → Only UDR routes active: 0.0.0.0/0 → Firewall → All traffic including on-premises bound goes through Firewall ✅
Forgetting this setting is one of the most common misconfiguration errors in hub and spoke deployments — on-premises traffic silently bypasses the Firewall even though the UDR looks correct.
UDR on GatewaySubnet — On-Premises to Spoke
To inspect traffic arriving from on-premises destined for spoke VNets, a UDR must also be applied to the GatewaySubnet:
Route Table: rt-gateway-subnetAssociated to: GatewaySubnetRoutes: Name Prefix Next hop type Next hop IP ──────────────────────────────────────────────────────────────── to-spoke1 10.1.0.0/16 VirtualAppliance 10.0.1.4 to-spoke2 10.2.0.0/16 VirtualAppliance 10.0.1.4 to-spoke3 10.3.0.0/16 VirtualAppliance 10.0.1.4
Note this uses specific spoke prefixes rather than 0.0.0.0/0 — applying a default route to GatewaySubnet breaks the gateway’s ability to communicate with Azure control plane endpoints.
Effective Route Inspection
You can verify UDRs are working correctly by checking a VM’s effective routes in the Azure portal or CLI:
az network nic show-effective-route-table \ --resource-group rg-spoke1 \ --name vm-prod-01-nic \ --output tableSource State Address Prefix Next Hop Type Next Hop IP──────── ─────── ──────────────── ────────────────── ──────────Default Active 10.1.0.0/16 VnetLocalDefault Invalid 0.0.0.0/0 Internet ← overriddenUser Active 0.0.0.0/0 VirtualAppliance 10.0.1.4 ✅Default Active 10.0.0.0/16 VNetPeeringDefault Active 10.2.0.0/16 VNetPeering
The default 0.0.0.0/0 → Internet system route shows as Invalid — it has been overridden by the custom UDR pointing to the Firewall. This confirms all traffic is force-tunnelled correctly.
Common Misconfiguration Pitfalls
Forgetting disableBgpRoutePropagation — gateway-learned routes override UDRs for on-premises prefixes, silently bypassing Firewall for hybrid traffic.
Missing return path UDR — if Spoke 2 subnet has no UDR, return traffic goes directly back to Spoke 1 via peering, creating an asymmetric routing loop that breaks TCP sessions.
Applying UDR to AzureBastionSubnet — Bastion requires direct internet connectivity for its management plane. A UDR with 0.0.0.0/0 → Firewall on AzureBastionSubnet breaks Bastion entirely. Bastion subnet must have no UDR or a very specific one that excludes Bastion management ranges.
Applying 0.0.0.0/0 UDR to GatewaySubnet — breaks gateway health probes and control plane communication. Always use specific spoke prefixes on GatewaySubnet, never a default route.
Firewall private IP not static — Azure Firewall’s private IP should be configured as static during deployment. If it changes, every UDR next-hop entry becomes invalid and traffic black-holes.
Key Takeaway
The UDR is a deceptively simple mechanism — a single route entry 0.0.0.0/0 → Virtual Appliance → 10.0.1.4 — that transforms Azure’s default direct peering behaviour into a fully inspected, security-enforced network. Applied correctly to every spoke subnet with disableBgpRoutePropagation enabled, it ensures no traffic — outbound internet, inbound DNAT, or lateral east-west — can bypass Azure Firewall, giving you complete visibility and control over your entire hub and spoke estate.