Networking
Azure Firewall: diagnose a shadowed rule before opening traffic
A production runbook for proving which Azure Firewall rule handles a flow, separating routing from policy order, and applying a targeted fix with validation and rollback.
An Azure Firewall policy can contain the rule that appears to allow a flow and still deny, bypass or handle that flow through another rule. The common reaction is to add a broader allow rule, lower a priority number or open an NSG. That may restore service, but it can also hide the real cause: the packet never reached the firewall, an inherited rule matched first, a network rule terminated processing before an application rule, or DNAT changed the path being investigated.
The use case is a production workload in an Azure spoke that calls an internal or external API through a hub firewall. The call worked before a policy deployment and now times out or returns an authorization-like error. DNS still resolves and the expected application rule is present. This runbook produces one decision: correct the route, correct the exact firewall rule, keep the policy unchanged, or roll back the last policy version.
Freeze one flow before reading the policy
Do not begin with hundreds of rules. Capture a single failed attempt as a five-tuple and attach the expected path. Use the IP address actually reached after DNS resolution, not only the FQDN written in the application configuration.
flow:
timestamp_utc: 2026-08-06T15:42:00Z
source_ip: 10.42.3.17
source_subnet: snet-app-prod
destination_fqdn: api.partner.example
resolved_ip: 203.0.113.24
destination_port: 443
protocol: TCP
expected_path:
next_hop: 10.0.1.4
firewall_policy: fwpolicy-hub-prod
rule_type: application
rule_collection: rc-app-approved-apis
rule: allow-partner-api
validation:
probe: GET /health
expected_status: 200
rollback_policy_version: fwpolicy-hub-prod-v41 Repeat the probe with a correlation identifier when the destination accepts one. Keep the timestamp narrow enough to find the same connection in firewall and application logs.
Prove the packet reaches the firewall
A missing firewall log is evidence. Before editing the policy, verify the source NIC’s effective route and the next hop selected for the destination. A route learned by BGP, a more specific UDR or a subnet associated with another route table can move the packet away from the expected appliance.
SOURCE_RG="rg-app-prod"
SOURCE_NIC="nic-app-01"
SOURCE_VM="vm-app-01"
SOURCE_IP="10.42.3.17"
DESTINATION_IP="203.0.113.24"
az network nic show-effective-route-table --resource-group "$SOURCE_RG" --name "$SOURCE_NIC" --output table
az network watcher show-next-hop --resource-group "$SOURCE_RG" --vm "$SOURCE_VM" --nic "$SOURCE_NIC" --source-ip "$SOURCE_IP" --dest-ip "$DESTINATION_IP" --output json Also check IP Flow Verify or effective NSG rules when the source is a supported VM path. A denied NSG and an unmatched firewall policy are different incidents. Do not compensate for one by weakening the other.
Reconstruct Azure Firewall processing order
Policy priority is not a single flat list. Azure Firewall evaluates rule types in a defined order. DNAT rules are processed before network rules, and network rules before application rules. Rules are terminating: once a matching rule handles the connection, later candidates do not get a chance. For inherited policies, parent collections are evaluated before child collections of the same type.
This creates a familiar failure. A broad network rule can match TCP 443 by destination IP before a more descriptive application rule can evaluate the FQDN. Moving the application collection to a numerically lower priority does not make it run before the matching network rule.
Read the policy in this order
1. Is this inbound traffic matching a DNAT rule?
2. Does an inherited network rule match the five-tuple?
3. Does a local network rule match it?
4. Only then, can an eligible application rule evaluate the FQDN?
5. If nothing matches, expect the default deny.
Do not infer precedence from
Portal display order alone
Rule names such as emergency or default
Application collection priority compared with a network collection
The presence of an allow rule that never received the flow Export the effective policy before changing it
Read the firewall’s attached policy, parent relationship, rule collection groups and priorities. Keep the JSON as the before-state for review and rollback.
FIREWALL_RG="rg-hub-prod"
FIREWALL_NAME="afw-hub-prod"
POLICY_NAME="fwpolicy-hub-prod"
az network firewall show --resource-group "$FIREWALL_RG" --name "$FIREWALL_NAME" --query "{name:name,policy:firewallPolicy.id,provisioningState:provisioningState}" --output json
az network firewall policy show --resource-group "$FIREWALL_RG" --name "$POLICY_NAME" --query "{name:name,parent:basePolicy.id,threatIntelMode:threatIntelMode,provisioningState:provisioningState}" --output json
az network firewall policy rule-collection-group list --resource-group "$FIREWALL_RG" --policy-name "$POLICY_NAME" --output json > firewall-policy-before.json If a parent policy exists, export it too. A local allow cannot override an inherited deny that already terminated the same rule type.
Use structured logs to identify the winning rule
Resource-specific Azure Firewall tables expose the action, policy, rule collection group, collection and rule. Query the exact window and flow before aggregating. Table availability depends on the diagnostic settings, so confirm collection rather than treating an empty query as proof of an allow.
let Start = datetime(2026-08-06T15:40:00Z);
let End = datetime(2026-08-06T15:45:00Z);
let Source = "10.42.3.17";
let Destination = "203.0.113.24";
union isfuzzy=true AZFWNetworkRule, AZFWApplicationRule, AZFWNatRule
| where TimeGenerated between (Start .. End)
| extend Src = tostring(column_ifexists("SourceIp", "")),
Dst = tostring(column_ifexists("DestinationIp", "")),
DstPort = tostring(column_ifexists("DestinationPort", "")),
Fqdn = tostring(column_ifexists("Fqdn", ""))
| where Src == Source
| where Dst == Destination or Fqdn has "api.partner.example"
| project TimeGenerated,
Type,
Action,
ActionReason,
Src,
Dst,
DstPort,
Fqdn,
Policy,
RuleCollectionGroup,
RuleCollection,
Rule
| order by TimeGenerated asc Interpret the result directly. A named deny points to a policy correction. Default Action means no rule matched. A hit on an unexpected allow can explain why an application rule has no counters. No row at all sends the investigation back to routing, diagnostics or the exact test window.
Classify the failure before proposing a change
No firewall event
Prove effective route, next hop and diagnostic settings
Do not add a firewall allow rule
Default deny
Compare the five-tuple and FQDN with the intended rule
Check protocol, port, source range and DNS resolution
Unexpected network rule matched
Review inherited and local network collections
Narrow or reorder within the network rule type
Do not try to outrank it with an application rule
Unexpected DNAT rule matched
Rebuild the translated destination and return path
Validate the published address and source restriction
Expected rule matched but the call still fails
Continue with TLS, destination authorization or application health
Keep the firewall policy unchanged This classification protects the change window. It prevents a route incident, TLS incident and firewall policy incident from being merged into one broad exception.
Apply the smallest reversible correction
A production correction should name the exact shadowing rule and the traffic that must continue to be blocked. Prefer narrowing a source, destination, port or FQDN over creating a high-priority catch-all allow. If the policy is managed as code, change the source definition and review the plan rather than patching the portal without a durable record.
change:
observed_rule: deny-unclassified-egress
intended_rule: allow-partner-api
correction: exclude approved destination from broad network deny
scope:
source: 10.42.3.0/24
destination: 203.0.113.24/32
port: 443
must_remain_blocked:
- other destinations on tcp/443
- non-production source subnets
evidence:
- before-policy export
- effective route and next hop
- matched firewall log row
- application probe result
rollback:
- restore policy version fwpolicy-hub-prod-v41
- replay blocked and allowed control probes Use a test or shadow policy when the platform design permits it. Otherwise, keep the scope narrow, deploy during an observed window and have the previous policy definition ready before the change starts.
Validate the allow and the guardrail
Validation needs two probes. The positive probe proves the intended application flow. The negative control proves the change did not open a neighboring destination or source. Then confirm the winning rule in Azure Firewall logs.
Validate
Intended source reaches the approved destination on the approved port
Firewall log names the intended allow rule
Neighboring destination remains denied by the expected rule
Route and next hop are unchanged
Application response and TLS are healthy
Rollback when
The broad deny no longer protects unrelated destinations
Another inherited or local rule starts matching unexpectedly
The intended flow still fails after the policy change
Policy deployment or logging becomes inconsistent
After rollback
Restore the previous policy definition
Replay positive and negative probes
Confirm the previous winning rules in logs
Keep the evidence pack attached to the incident Conclusion
An Azure Firewall rule is not effective because it exists; it is effective only if the packet reaches the firewall and the processing order lets that rule handle the flow. The useful evidence is therefore a five-tuple, effective route, next hop, exported policy and log row naming the winning rule.
The decision is then bounded: fix routing when the firewall never saw the packet, correct the exact rule when another collection terminated processing, leave the policy alone when the expected allow already matched, or restore the previous policy version when the guardrail test fails. That is safer than opening traffic until the symptom disappears.