Networking

Azure Firewall DNS proxy: diagnose egress before opening rules

A production runbook for qualifying an Azure egress failure with Firewall DNS proxy, FQDN resolution, UDRs, application rules, logs, validation and rollback before broadening traffic.

30 Jun 2026 azureazure-firewalldnsdns-proxyegressudrnetworkingobservabilitykqlrunbookrollback

An Azure egress incident often starts with a simple request: “open outbound access to this API”. The workload can no longer reach an external or private dependency, the Azure Firewall application rule appears to exist, and logs do not always show the same FQDN that was tested from an admin workstation. The risky reflex is to add a broader rule, an IP exception or a routing bypass.

The use case is an Azure spoke where subnets egress through a UDR to Azure Firewall. Application rules allow specific FQDNs, Firewall DNS proxy is expected to keep name resolution aligned with logs and filtering decisions, and several workloads use App Service VNet Integration, virtual machines, private CI jobs or containers. This runbook helps decide whether the incident is really a missing rule, DNS resolution bypassing the Firewall, routing, SNAT or a dependency-side change.

Describe the full egress path

Before touching rules, write down the expected path. An FQDN-based application rule is reliable only when the workload resolves the name through a path compatible with Azure Firewall. If a VM uses an internal resolver, App Service inherits a different DNS configuration, or a forwarder returns an address that no longer matches what the Firewall observes, opening another rule does not fix the cause.

text egress-path-scope.txt
Incident
Workload: api-orders-prod
Subnet: spoke-prod/app-integration
Dependency: api.partner.example
Expected path: subnet -> UDR -> Azure Firewall -> Internet or private dependency
Expected DNS path: workload -> Azure Firewall DNS proxy or approved resolver -> upstream DNS
Expected rule: application rule on api.partner.example over HTTPS

Questions before change
Which resolver does the workload use at runtime?
Is Azure Firewall DNS proxy enabled and advertised to this subnet?
Does the UDR force traffic to the Firewall for the dependency IP?
Do Firewall logs show the expected FQDN or only an IP flow?
Which validation proves the dependency works without broadening egress?

This description separates three topics: name resolution, routing to the Firewall and filtering decision. All three must be true before accepting the change.

Capture Firewall and DNS proxy state

The first check is the real Firewall configuration. Verify DNS proxy, configured DNS servers, the attached policy and the rule collections that carry workload egress.

bash 01-capture-firewall-dns-proxy.sh
FIREWALL_NAME="azfw-prod"
FIREWALL_RG="rg-network-prod"
POLICY_NAME="azfw-policy-prod"
POLICY_RG="rg-network-prod"

az network firewall show --name "$FIREWALL_NAME" --resource-group "$FIREWALL_RG" --query "{name:name,privateIp:ipConfigurations[0].privateIPAddress,dnsSettings:dnsSettings,firewallPolicy:firewallPolicy.id}" --output json

az network firewall policy show --name "$POLICY_NAME" --resource-group "$POLICY_RG" --query "{name:name,dnsSettings:dnsSettings,threatIntelMode:threatIntelMode}" --output json

az network firewall policy rule-collection-group list --policy-name "$POLICY_NAME" --resource-group "$POLICY_RG" --query "[].{name:name,priority:priority,provisioningState:provisioningState}" --output table

Attach this capture to the incident. If DNS proxy is disabled, or if workloads do not use it, FQDN rules become harder to explain. They may still work in some cases, but the team loses alignment between resolution, logs and policy.

Prove the resolver used by the workload

The useful test does not start from an admin workstation. It starts from the subnet, runtime or runner that fails. The goal is to know which resolver answers, which address is returned and whether that address then follows the UDR to the Firewall.

bash 02-workload-dns-and-route-check.sh
TARGET_FQDN="api.partner.example"
EXPECTED_FIREWALL_IP="10.40.0.4"

printf "Resolver configuration
"
cat /etc/resolv.conf || true

printf "Resolved addresses
"
getent hosts "$TARGET_FQDN" || nslookup "$TARGET_FQDN"

printf "Route selected toward resolved IP
"
TARGET_IP="$(getent hosts "$TARGET_FQDN" | awk '{print $1}' | head -1)"
ip route get "$TARGET_IP"

printf "TCP probe
"
timeout 5 bash -c "cat < /dev/null > /dev/tcp/$TARGET_FQDN/443"

For App Service or Functions with VNet Integration, run the test from a context close to runtime: Kudu console when relevant, a diagnostic job, a temporary container or a synthetic probe in the same spoke. For a private CI runner, the preflight should run before deployment.

Read Firewall logs as path evidence

An Azure Firewall application rule should leave a trace with the FQDN, action, collection and rule. If logs only show a network flow to an IP, or show nothing, the incident may not be a missing application rule.

kusto 03-firewall-egress-evidence.kql
let TargetFqdn = "api.partner.example";
let WindowStart = ago(2h);
AZFWApplicationRule
| where TimeGenerated > WindowStart
| where Fqdn has TargetFqdn or TargetUrl has TargetFqdn
| project TimeGenerated, SourceIp, Fqdn, TargetUrl, Action, RuleCollection, Rule, Protocol, DestinationPort, Policy
| order by TimeGenerated desc

Add network logs when the application does not appear in application rules.

kusto 04-firewall-network-flow-check.kql
let WorkloadPrefix = "10.42.";
let WindowStart = ago(2h);
AZFWNetworkRule
| where TimeGenerated > WindowStart
| where SourceIp startswith WorkloadPrefix
| project TimeGenerated, SourceIp, DestinationIp, DestinationPort, Action, RuleCollection, Rule, Protocol
| order by TimeGenerated desc

If the flow appears in a network rule toward an IP while policy expects FQDN application traffic, the next question is DNS and traffic classification, not only port opening.

Separate common causes

The diagnosis should converge on a testable cause. Symptoms look similar, but corrections are different.

text egress-diagnosis-matrix.txt
DNS proxy or resolver
The workload does not point to the expected DNS proxy
The internal resolver does not forward as intended
The FQDN resolves to different IPs depending on the subnet
Firewall logs do not contain the expected FQDN

Routing
The UDR does not cover the resolved prefix
A more specific route bypasses the Firewall
Return traffic follows an asymmetric path
The test succeeds from another subnet but fails from runtime

Firewall policy
The FQDN rule exists but in a lower-priority collection
Port or protocol does not match the real flow
A higher-priority deny rule catches the request
The effective FQDN is an alias, CDN name or different subdomain

Dependency or identity
The connection arrives but the API returns 401, 403 or 429
Certificate, SNI or expected hostname does not match
The partner changed IP, domain or rate-limit policy

This matrix prevents broad exceptions. An IP added under pressure can hide a DNS issue, bypass the FQDN intent and make the next incident harder to understand.

Validate a minimal correction

The correction must stay targeted. Depending on the cause, it may enable or restore DNS proxy usage, fix the subnet resolver, add an explicit forwarder, adjust a UDR, correct the exact FQDN or move an application rule.

yaml egress-change-guardrails.yml
before_change:
- capture_firewall_policy_and_dns_settings
- prove_runtime_resolver
- prove_effective_route_to_resolved_ip
- collect_application_and_firewall_logs

allowed_corrections:
- restore_dns_proxy_usage_for_subnet
- fix_forwarder_for_expected_domain
- add_exact_fqdn_application_rule
- adjust_rule_priority_with_evidence
- correct_udr_to_firewall_for_dependency_prefix

block_change_when:
- requested_rule_uses_wildcard_without_examples
- only_admin_workstation_test_passes
- rollback_state_is_missing
- firewall_logs_do_not_show_attempts_from_workload
- dependency_error_is_authentication_not_network

After the correction, replay the test from the workload and read the logs again. Validation should show the expected FQDN, the expected rule, the Allow action and then application success.

Decide: fix, open or roll back

Keep the decision simple. Open a rule only when resolution and routing are proven and the actually called FQDN is known. Fix the DNS or UDR path when logs do not match the expected model. Roll back when the change makes the path less explainable.

text egress-decision.txt
Accept change
Workload uses the approved resolver or Firewall DNS proxy
Resolved IP uses the expected UDR toward Azure Firewall
Firewall logs show the target FQDN and the intended rule
Application probe succeeds without wildcard or broad IP exception
Monitoring shows no new deny or timeout burst

Fix before opening more
Resolver differs between runtime and diagnostic host
FQDN is absent from application rule logs
Network rule logs show raw IP flows instead of expected FQDN traffic
Rule priority or protocol does not match the real request

Rollback
DNS proxy change breaks another spoke
UDR change redirects unrelated traffic
Wildcard or IP exception was added without evidence
Application still fails after the targeted correction
Previous resolver, route or rule state is known and restorable

Rollback is not complete when the rule is removed. It is complete when resolver behavior, effective route, Firewall logs and the application test return to the expected state.

Conclusion

Azure Firewall should not become a list of exceptions added under pressure. When egress fails on an FQDN, the runbook should first prove the resolver used, the UDR path, FQDN visibility in logs and the rule actually applied.

The right decision is rarely “open wider”. It is to restore a DNS path that stays coherent with the Firewall, allow the exact FQDN when needed, validate from runtime and keep rollback clear. That is how Azure egress stays operable without losing network explainability.