Symptoms
- Security expects all traffic between spokes and PaaS private endpoints to pass the hub firewall. Firewall logs show nothing for those flows.
- Firewall application or network rules for a private endpoint IP have no effect.
- After someone "fixes" the routing, connections to private endpoints start timing out.
Why it happens
When you create a private endpoint, Azure adds a /32 system route for its IP, with next hop type InterfaceEndpoint, to the endpoint's VNet and to peered VNets.
Routing uses the longest prefix match. A UDR like 10.0.0.0/8 -> firewall is less specific than the /32, so the system route wins and traffic goes straight to the endpoint, around the firewall.
See it on a spoke VM's NIC:
az network nic show-effective-route-table -g rg-spoke1 -n vm-app01-nic \
--query "value[?nextHopType=='InterfaceEndpoint'].{prefix:addressPrefix[0],state:state,source:source}" -o table
Step 1: decide what you actually need
| Requirement | Approach |
|---|---|
| Inspect/log all spoke → PaaS traffic | Route via firewall (steps below) |
| Only control who can connect | NSGs on the private endpoint subnet (with network policies) may be enough |
| Reduce cost and latency | Leave the direct path, use NSGs and PaaS-side auth |
Routing through Azure Firewall adds processing cost and latency for every byte; decide deliberately.
Step 2: enable route table policies on the endpoint subnet
Private endpoint network policies are disabled by default. Enabling route table support lets UDRs override the /32 route.
az network vnet subnet update -g rg-hub --vnet-name vnet-hub -n snet-private-endpoints \
--private-endpoint-network-policies Enabled # or RouteTableEnabled / NetworkSecurityGroupEnabled
Values: Disabled, Enabled (both NSG and route table), NetworkSecurityGroupEnabled, RouteTableEnabled.
With route table policies on the endpoint's subnet, the /32 routes in that VNet and peered VNets can be overridden by a UDR with an equal or less specific prefix. After the change, the effective route list shows the /32 InterfaceEndpoint route as Invalid where your UDR covers it.
Step 3: add the UDRs on the client side
On each spoke subnet that should be inspected:
az network route-table route create -g rg-spoke1 --route-table-name rt-spoke1 \
-n to-private-endpoints --address-prefix 10.0.20.0/24 \
--next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4 # firewall private IP
Using the private endpoint subnet prefix keeps the route table small and covers new endpoints automatically.
Check again:
az network nic show-effective-route-table -g rg-spoke1 -n vm-app01-nic -o table | grep -E "10.0.20|InterfaceEndpoint"
The UDR should be Active and the /32 routes Invalid.
Step 4: keep the return path symmetric
The reply from the private endpoint goes back to the client by normal routing, not through the firewall, unless something sends it there. The firewall then sees only one direction and may drop it, or the client sees a reply from an unexpected path.
Two options:
- SNAT on the firewall (recommended for private endpoint traffic): the endpoint sees the firewall as the source, so replies return to it. In Azure Firewall, network rules SNAT traffic to private IP ranges only when configured; application rules always SNAT. Microsoft's guidance for inspecting private endpoint traffic recommends SNAT. Configure the private SNAT ranges accordingly:
az network firewall policy update -g rg-hub -n fwpol-hub --private-ranges 255.255.255.255/32
255.255.255.255/32 means "always SNAT". Check this fits your design before applying it to all traffic.
- UDR on the endpoint subnet back to the firewall for client prefixes. Private endpoints themselves don't use the subnet's UDRs for return traffic in all cases; test carefully. SNAT is simpler and predictable.
Step 5: allow it on the firewall
Add rules for the endpoint traffic, for example an application rule for HTTPS to storage FQDNs, or network rules to the endpoint subnet on the right ports:
az network firewall policy rule-collection-group collection add-filter-collection \
-g rg-hub --policy-name fwpol-hub --rcg-name rcg-spokes --name pe-access \
--collection-priority 300 --action Allow --rule-type NetworkRule --rule-name sql-pe \
--source-addresses 10.1.0.0/16 --destination-addresses 10.0.20.0/24 \
--ip-protocols TCP --destination-ports 1433 443
Then check the firewall logs:
AZFWNetworkRule
| where TimeGenerated > ago(30m) and DestinationIp startswith "10.0.20."
| project TimeGenerated, SourceIp, DestinationIp, DestinationPort, Action, Rule
Troubleshooting after the change
| Symptom | Cause | Fix |
|---|---|---|
| Still bypassing | Network policies not enabled on the endpoint subnet, or UDR more specific than needed | Enable policies; check effective routes |
| Timeouts after routing via firewall | No SNAT, asymmetric return | Enable SNAT for these flows |
| Denied in firewall logs | Missing rule | Add network/application rule |
| DNS works but connection fails | Firewall DNS proxy / FQDN rules resolving differently | Make firewall and clients use the same DNS (private zones) |
Warning
Change routing for private endpoints in a maintenance window. The moment the UDR becomes active, every connection to those endpoints goes through the firewall; missing rules or SNAT cause an outage.
Prevent it next time
- Decide per environment whether PaaS traffic is inspected, and document it.
- Create private endpoint subnets with network policies enabled from the start.
- Route by private endpoint subnet prefix, not per-endpoint
/32s. - Include "check effective routes" in the acceptance test for every new spoke.