Home / Azure

AzureTroubleshootingAdvanced

Private endpoint traffic bypasses Azure Firewall: /32 routes, subnet network policies and asymmetric return

Why traffic to an Azure private endpoint skips your firewall in a hub-and-spoke network, how the injected /32 route and subnet network policies work, and how to force inspection without breaking return traffic.

Deepak Bhardwaj5 min read

Applies toAzure Private Link / private endpoints, Azure Firewall or NVAs, hub-and-spoke VNets with peering, Azure CLI 2.x

Symptom

You built hub-and-spoke with a UDR 0.0.0.0/0 -> Azure Firewall (and spoke-to-spoke routes via the firewall). Everything is inspected except traffic to private endpoints:

  • Firewall logs show nothing for SQL, Storage or Key Vault private endpoint connections.
  • Or the reverse: after you "fix" routing, connections to the private endpoint start timing out.

Why it happens

When a private endpoint is created, Azure injects a /32 system route for its IP into the endpoint's VNet and every peered VNet, with next hop InterfaceEndpoint. Longest prefix wins, so a /32 beats your 10.0.0.0/8 -> firewall UDR. Traffic goes straight to the endpoint.

The private endpoint subnet also has a property, privateEndpointNetworkPolicies, which controls whether NSGs and UDRs apply to private endpoints:

Value NSG applies to PE UDR with a broader prefix overrides the /32
Disabled (old default) No No
NetworkSecurityGroupEnabled Yes No
RouteTableEnabled No Yes
Enabled Yes Yes

Step 1: confirm what the client's NIC actually uses

az network nic show-effective-route-table -g rg-spoke1 -n vm-app01-nic -o table \
  | grep -E "InterfaceEndpoint|VirtualAppliance"

If you see 10.20.4.5/32 InterfaceEndpoint (the PE IP), the client is going direct.

Check the endpoint subnet setting:

az network vnet subnet show -g rg-spoke2 --vnet-name vnet-spoke2 -n snet-pe \
  --query privateEndpointNetworkPolicies -o tsv

Step 2: decide where inspection should happen

You have two common designs:

  1. Inspect on the client side: the client's subnet gets a UDR that sends PE traffic to the firewall. This requires network policies on the PE subnet (that is where the /32 is invalidated) and a UDR on the client subnet.
  2. Don't inspect PE traffic at all: rely on NSGs on the PE subnet plus the PaaS service's own firewall. Simpler, and fine for many teams. Document it as an explicit decision.

Step 3: enable network policies on the PE subnet

az network vnet subnet update -g rg-spoke2 --vnet-name vnet-spoke2 -n snet-pe \
  --private-endpoint-network-policies Enabled

With RouteTableEnabled or Enabled, a UDR with a broader prefix (for example the PE subnet /24) now overrides the injected /32 in the VNets that carry the UDR.

Add the route on the client subnet's route table:

az network route-table route create -g rg-spoke1 --route-table-name rt-spoke1 \
  -n to-pe-subnet --address-prefix 10.20.4.0/24 \
  --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4

Warning

Change routing in a maintenance window. Moving PE traffic onto the firewall without the firewall rules in place drops every database and storage connection from that spoke.

Step 4: keep the return path symmetric

The private endpoint replies straight to the client IP. If the request went through the firewall but the reply doesn't, the firewall sees half a TCP session and drops it, and the client times out.

Fix it with one of:

  • SNAT on the firewall for traffic to private endpoints, so replies come back to the firewall. With Azure Firewall, application rules SNAT by default. For network rules, configure the private IP ranges (Private IP ranges (SNAT)) so the PE range is not treated as private, which forces SNAT. Check the current firewall policy docs for your SKU.
  • A UDR on the PE subnet pointing the client range back to the firewall. This only works if network policies are RouteTableEnabled/Enabled there.

SNAT is usually simpler. The trade-off is that the PaaS service (and its logs) sees the firewall's IP instead of the client's.

Step 5: test

# From the client VM: name should resolve to the PE IP, and the connection should succeed
nslookup sqlprod01.database.windows.net
nc -vz sqlprod01.database.windows.net 1433

Then confirm in the firewall logs:

AZFWNetworkRule
| where TimeGenerated > ago(30m)
| where DestinationIp == "10.20.4.5"
| project TimeGenerated, SourceIp, DestinationIp, DestinationPort, Action

Troubleshooting table

Symptom Cause Fix
Firewall never sees PE traffic /32 InterfaceEndpoint route wins Enable network policies on the PE subnet and add a broader UDR on the client subnet
UDR added, still direct UDR prefix is a /32 in a VNet where policies are off, or UDR is on the wrong subnet Policies go on the PE subnet; the UDR goes on the client subnet
Connections time out after the change Asymmetric return path SNAT on the firewall, or a return UDR on the PE subnet
Works from spoke, fails from on-premises Gateway subnet route table missing the PE prefix Add the PE route to the GatewaySubnet route table
NSG on PE subnet ignored Policies Disabled or RouteTableEnabled Use NetworkSecurityGroupEnabled or Enabled

Prevent it next time

  • Create every private endpoint subnet with --private-endpoint-network-policies Enabled from your landing zone templates.
  • Write down per spoke whether PE traffic is inspected or not, so nobody "fixes" one direction only.
  • Keep DNS separate from routing: name resolution problems belong in private endpoint DNS, routing problems here.
  • Related: Azure Firewall SNAT port exhaustion.