To build a hub-and-spoke network with centralised egress, deploy Azure Firewall in a hub virtual network, peer each spoke to the hub with forwarded traffic allowed, and associate a route table with every spoke subnet whose default route (0.0.0.0/0) points at the firewall's private IP as a Virtual appliance next hop with gateway route propagation disabled. If the hub also has a VPN or ExpressRoute gateway, add a route table on GatewaySubnet that sends spoke prefixes to the firewall, so on-premises traffic is inspected in both directions. Forced tunnelling to an on-premises firewall additionally needs the Firewall Management NIC.
Who this is for and what you will have
This guide is for network and platform engineers building the connectivity layer of an Azure landing zone, or retrofitting a firewall into peered virtual networks that currently route directly. At the end you will have:
- A hub with Azure Firewall, a Firewall Policy and, optionally, a VPN or ExpressRoute gateway.
- Spokes peered to the hub with the correct gateway transit and forwarding settings.
- Route tables that force spoke-to-internet, spoke-to-spoke and on-premises-to-spoke traffic through the firewall.
- Logging, verification commands and fixes for the routing mistakes that cause most outages.
If you are planning a larger migration, the enterprise Azure cloud migration playbook covers where this network fits in the overall sequence.
Architecture and address plan
On-premises 192.168.0.0/16
|
VPN or ExpressRoute
|
+--------------------------- Hub 10.0.0.0/16 ----------------------------+
| GatewaySubnet 10.0.1.0/26 route table: spokes -> firewall |
| AzureFirewallSubnet 10.0.0.0/26 Azure Firewall (private IP noted) |
| AzureFirewallManagementSubnet 10.0.2.0/26 (only for forced tunnel) |
| AzureBastionSubnet 10.0.3.0/26 (optional) |
+-------------------------------------------------------------------------+
| peering | peering
+------ Spoke 1 10.1.0.0/16 ------+ +------ Spoke 2 10.2.0.0/16 ------+
| snet-app route 0/0 -> firewall | | snet-app route 0/0 -> firewall |
+---------------------------------+ +---------------------------------+| Subnet | Minimum size | Notes |
|---|---|---|
| AzureFirewallSubnet | /26 | Required name; NSGs not supported; /26 covers all firewall scaling |
| AzureFirewallManagementSubnet | /26 | Only when the Management NIC is enabled |
| GatewaySubnet | /26 recommended | Required name for VPN and ExpressRoute gateways |
| Spoke workload subnets | Your choice | Each gets a route table |
Plan non-overlapping address space across every hub, spoke and on-premises range before you deploy. Use one hub per region and connect only spokes from the same region to it.
Prerequisites
- Permissions to create peerings and route tables on the hub and spoke virtual networks, including in other subscriptions if spokes live there.
- The firewall, its virtual network and its public IP in the same subscription; the firewall and virtual network in the same resource group.
- The Az PowerShell module for the scripted steps.
- A decision on the firewall tier. Standard covers most egress filtering; Premium adds TLS inspection, IDPS and URL filtering. Microsoft states throughput starts at 2.5 to 3 Gbps and scales automatically to 30 Gbps on Standard or 100 Gbps on Premium.
- A decision on forced tunnelling. Standard and Premium firewalls need the Management NIC enabled at creation, or a stop and start to add it later.
Step 1: Create the hub virtual network
Create the hub with AzureFirewallSubnet (in the portal, set Subnet purpose to Azure Firewall), GatewaySubnet if you need cross-premises connectivity, and AzureFirewallManagementSubnet if you'll force-tunnel internet traffic. Leave all three without NSGs. The Management subnet gets a system route table; Microsoft warns against attaching your own, and if you do, it must contain a default route to the internet.
Step 2: Deploy Azure Firewall with a Firewall Policy
In the portal, create a Firewall with:
- Firewall tier: Standard or Premium
- Firewall management: Use a Firewall Policy to manage this firewall, with a new policy in the same region
- Choose a virtual network: the hub
- Public IP address: a new Standard public IP
- Enable Firewall Management NIC: enable only if you need forced tunnelling or want no public IP on the data path, and supply the management subnet and a management public IP
When deployment finishes, record the firewall's private IP address; every route table points at it. Treat that address as something that can change: Microsoft notes that stopping and starting a firewall can move it to a different address in the subnet, which breaks route tables that reference the old one.
Turn on diagnostic settings now. Choose Resource specific as the destination table so network, application and DNS proxy logs land in dedicated tables such as AZFWNetworkRule and AZFWApplicationRule. Logs can take up to 30 minutes to start appearing.
Step 3: Peer each spoke to the hub
On the hub virtual network, select Peerings > Add and configure both directions in one step:
| Setting | Hub-to-spoke link | Spoke-to-hub link |
|---|---|---|
| Allow access to the remote virtual network | Selected | Selected |
| Allow receiving forwarded traffic | Selected | Selected |
| Allow gateway or route server to forward traffic | Selected (hub has a gateway) | Not selected |
| Use the remote virtual network's gateway or route server | Not selected | Selected (hub has a gateway) |
The same in PowerShell, assuming $hub and $spoke1 hold the virtual network objects:
Add-AzVirtualNetworkPeering -Name 'peer-hub-to-spoke1' `
-VirtualNetwork $hub -RemoteVirtualNetworkId $spoke1.Id `
-AllowForwardedTraffic -AllowGatewayTransit
Add-AzVirtualNetworkPeering -Name 'peer-spoke1-to-hub' `
-VirtualNetwork $spoke1 -RemoteVirtualNetworkId $hub.Id `
-AllowForwardedTraffic -UseRemoteGatewaysLeave out -AllowGatewayTransit and -UseRemoteGateways if the hub has no gateway. Peering is non-transitive: after this step each spoke can reach the hub, but not the other spokes and not the internet through the firewall. Routing does that.
Step 4: Route spoke traffic to the firewall
Create one route table per spoke (or a shared one per region) with Propagate gateway routes set to No, a default route to the firewall, and associate it with every workload subnet. Disabling propagation stops routes learned from on-premises over BGP from overriding your default route; if you keep propagation on, add specific routes to the firewall for every on-premises prefix.
$fwIp = '<firewall-private-ip>'
$rt = New-AzRouteTable -Name 'rt-spoke1' -ResourceGroupName 'rg-spoke1' `
-Location 'westeurope' -DisableBgpRoutePropagation
$rt | Add-AzRouteConfig -Name 'default-to-firewall' -AddressPrefix '0.0.0.0/0' `
-NextHopType 'VirtualAppliance' -NextHopIpAddress $fwIp | Set-AzRouteTable
$subnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $spoke1 -Name 'snet-app'
$subnet.RouteTable = $rt
$spoke1 | Set-AzVirtualNetworkHow this interacts with Azure's own routes:
- Azure picks the longest matching prefix. Among routes with the same prefix, a user-defined route beats a BGP route, which beats a system route.
- Your
0.0.0.0/0UDR replaces the default internet route, and Azure also removes the default None routes for 10.0.0.0/8, 192.168.0.0/16 and 100.64.0.0/10 from that subnet. Traffic to other spokes therefore matches no more specific route, so it goes to the firewall, which forwards it to the destination spoke over its own peering. That is how spokes talk to each other through the hub. - Traffic to the hub's own address space matches the more specific peering route and goes direct. If you also peer two spokes directly, traffic between them goes direct too. To force those flows through the firewall, add a UDR for the destination subnet prefix on both sides.
- A UDR with a
0.0.0.0/0next hop of Virtual appliance also sends traffic for Azure services' public IPs to the firewall, unless you've enabled a service endpoint on the subnet.
Microsoft's recommended way to segment subnets inside one virtual network is NSGs, not UDRs through the firewall, because per-subnet routes are easy to get wrong.
Step 5: Send on-premises traffic through the firewall
Without extra routes, traffic arriving from on-premises through the hub gateway goes straight to the spokes, because the gateway subnet has peering routes to them. Create a route table for GatewaySubnet with one route per spoke prefix, next hop Virtual appliance at the firewall IP, and leave Propagate gateway routes enabled:
$rtGw = New-AzRouteTable -Name 'rt-gatewaysubnet' -ResourceGroupName 'rg-hub' -Location 'westeurope'
$rtGw | Add-AzRouteConfig -Name 'to-spoke1' -AddressPrefix '10.1.0.0/16' `
-NextHopType 'VirtualAppliance' -NextHopIpAddress $fwIp | Set-AzRouteTable
$gwSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $hub -Name 'GatewaySubnet'
$gwSubnet.RouteTable = $rtGw
$hub | Set-AzVirtualNetworkTwo rules for this subnet come straight from Microsoft's routing documentation: never disable route propagation on GatewaySubnet, or the gateway stops working, and never put a 0.0.0.0/0 route in a route table associated with the VPN gateway subnet. The spoke-side default route from Step 4 already sends return traffic to the firewall, so the flow is symmetric.
AzureFirewallSubnet itself needs no route table; it learns on-premises routes over BGP. The exception follows in the forced tunnelling section.
Step 6: Write the firewall rules
Rule collections are processed by priority, with DNAT collections before network collections and network before application collections. All rules are terminating, and anything not allowed is denied. A sensible starting policy:
- A network rule collection for east-west and hybrid flows, for example on-premises
192.168.0.0/16to spoke10.1.0.0/16on TCP 443 and 3389. - An application rule collection for internet egress by FQDN, for example operating system update sources for the spoke subnets.
- No broad "any to any" rules. Allow only the destinations, ports and FQDNs each workload actually needs.
If you use FQDNs in network rules, enable DNS proxy on the firewall and set each virtual network's custom DNS server to the firewall's private IP, then restart the VMs to pick it up. The firewall listens on port 53 and forwards queries to Azure DNS or your custom servers, so it resolves names the same way the clients do. Microsoft's architecture guidance gives the same reason for application rules: clients and firewall must use the same DNS.
SNAT behaviour matters for the systems behind the firewall. Azure Firewall doesn't SNAT traffic to RFC 1918 or RFC 6598 destinations, so spokes and on-premises hosts see real client addresses. Application rule traffic is always SNATed; use network rules with FQDN destinations if you need the original source address in logs. Internet-bound traffic leaves from one of the firewall's public IPs, which partners must allow-list.
Forced tunnelling to an on-premises firewall
If policy requires internet traffic to exit through an on-premises security stack, Azure Firewall must have the Management NIC. Then add routes on AzureFirewallSubnet toward your on-premises device, or enable Propagate gateway routes so the default route arrives over BGP. With forced tunnelling, internet-bound traffic is SNATed to a firewall private IP, which hides the original source from the on-premises firewall; to stop that, set the private IP range to 0.0.0.0/0, after which the firewall never egresses directly to the internet. Microsoft states that DNAT isn't supported with forced tunnelling enabled because of asymmetric routing, although a firewall that only has the Management NIC enabled still supports DNAT.
Without the Management NIC the opposite applies: Azure Firewall needs direct internet connectivity, so if AzureFirewallSubnet learns a default route from on-premises over BGP, override it with a 0.0.0.0/0 UDR whose next hop type is Internet. To add the Management NIC to an existing Standard or Premium firewall, create the management subnet and public IP, then deallocate and reallocate the firewall during a maintenance window:
$azfw = Get-AzFirewall -Name 'afw-hub' -ResourceGroupName 'rg-hub'
$azfw.Deallocate()
Set-AzFirewall -AzureFirewall $azfw
$azfw = Get-AzFirewall -Name 'afw-hub' -ResourceGroupName 'rg-hub'
$vnet = Get-AzVirtualNetwork -Name 'vnet-hub' -ResourceGroupName 'rg-hub'
$pip = Get-AzPublicIpAddress -Name 'pip-afw' -ResourceGroupName 'rg-hub'
$mgmtPip = Get-AzPublicIpAddress -Name 'pip-afw-mgmt' -ResourceGroupName 'rg-hub'
$azfw.Allocate($vnet, $pip, $mgmtPip)
$azfw | Set-AzFirewallAfterwards, confirm the firewall's private IP hasn't changed before you trust your route tables.
Verification
Check what a VM will actually do with a packet:
Get-AzEffectiveRouteTable -NetworkInterfaceName 'nic-app01' -ResourceGroupName 'rg-spoke1' |
Format-Table
$vm = Get-AzVM -Name 'vm-app01' -ResourceGroupName 'rg-spoke1'
$nw = Get-AzNetworkWatcher -Name 'NetworkWatcher_westeurope' -ResourceGroupName 'NetworkWatcherRG'
Get-AzNetworkWatcherNextHop -NetworkWatcher $nw -TargetVirtualMachineId $vm.Id `
-SourceIPAddress '10.1.0.4' -DestinationIPAddress '10.2.0.4'The 0.0.0.0/0 user route should be Active, the default internet route Invalid, and the next hop for another spoke VirtualAppliance at the firewall IP. Then confirm the firewall saw the flow:
AZFWNetworkRule
| where TimeGenerated > ago(1h)
| where SourceIp startswith "10.1." and DestinationIp startswith "10.2."
| project TimeGenerated, SourceIp, DestinationIp, DestinationPort, Protocol, Action, RuleCollection, Rule, ActionReason
| order by TimeGenerated descFinally, test from on-premises to a spoke VM and check that the flow appears in the logs with the expected rule.
Troubleshooting
Spoke VMs lost internet access after the route table was applied. That's expected until an application or network rule allows the traffic. Look for denied flows in AZFWApplicationRule and AZFWNetworkRule; in the network rule table, an ActionReason of Default Action means no rule matched.
Spoke-to-spoke works but never appears in the firewall logs. The spokes are peered directly, or the route table isn't associated with the source subnet. Check effective routes on both NICs.
On-premises users reach spokes, but replies are dropped. Asymmetric routing: the gateway subnet has no UDR for the spoke prefixes, so requests bypass the firewall while replies hit it via the spoke's default route. Add the GatewaySubnet routes from Step 5.
The VPN gateway stopped working after routing changes. Someone disabled propagation on GatewaySubnet or added a 0.0.0.0/0 route there. Remove it.
The firewall can't reach the internet and metrics stop arriving. AzureFirewallSubnet learned a default route from on-premises. Add the 0.0.0.0/0 Internet UDR or move to forced tunnelling with the Management NIC. Microsoft lists published default routes as a cause of missing firewall metrics.
Everything broke after the firewall was stopped and started. The private IP changed. Update the next hop in every route table.
A TCP ping succeeds even though no rule allows it. With no matching allow rule, the firewall answers TCP pings itself and doesn't log them. Test with a real application connection.
Long-lived idle connections drop. The TCP idle timeout is four minutes and isn't user-configurable for east-west traffic. Use TCP keep-alives.
Closing checklist
- Non-overlapping address plan; one hub per region.
- AzureFirewallSubnet and, if needed, AzureFirewallManagementSubnet at /26, with no NSGs.
- Firewall Policy, resource-specific diagnostic logs, and the firewall private IP documented.
- Peerings allow forwarded traffic; gateway transit on the hub side and remote gateway on spokes.
- Spoke route tables with
0.0.0.0/0to the firewall and propagation disabled. GatewaySubnetroute table with spoke prefixes to the firewall, propagation enabled, no default route.- DNS proxy enabled if network rules use FQDNs.
- Forced tunnelling only with the Management NIC; otherwise an Internet UDR if BGP injects a default route.
References
- Hub-spoke network topology in Azure
- Tutorial: Deploy and configure Azure Firewall and policy in a hybrid network
- Deploy and configure Azure Firewall in a hybrid network by using PowerShell
- Azure virtual network traffic routing
- Azure Firewall forced tunneling
- Azure Firewall Management NIC
- Azure Firewall DNS settings
- Azure Firewall FAQ
- Monitor Azure Firewall
- AZFWNetworkRule table reference
- Diagnose a VM network routing problem with Azure PowerShell