Cloud

Azure App Service: diagnose egress IP drift before changing an allowlist

A production runbook for qualifying App Service egress IP drift with VNet Integration, NAT Gateway, DNS, UDRs, destination logs, validation and rollback before changing a partner allowlist.

13 Jul 2026 azureapp-servicevnet-integrationnat-gatewayegressoutbound-ipallowlistdnsudrobservabilityrunbookrollbackproduction

App Service egress IP drift often looks like an application bug or a partner-side error. The app still works for some dependencies, but an external API returns 403, a SaaS endpoint rejects calls, or a webhook no longer sees the expected source address. The risky reflex is to add the new IP to the allowlist quickly and move on.

The use case is an App Service integrated with a VNet and calling a partner API through an integration subnet, possibly a UDR, Azure Firewall and NAT Gateway. The runbook goal is to prove which IP the destination actually sees, why it changed, and whether the right decision is a network correction, a controlled allowlist update, or rollback.

Freeze the egress contract

Start by writing the expected egress contract. An allowlist incident cannot be diagnosed from “the app no longer works”. Tie the application, hostname, port, subnet, egress path and expected public IP together.

text app-service-egress-contract.txt
Flow to qualify
Application: app-orders-prod
Plan: asp-prod-linux
Slot: production
Destination: api.partner.example / TCP 443
Integration subnet: snet-appservice-egress-prod
Expected path: App Service -> VNet Integration -> NAT Gateway -> Internet
Expected public IP: 203.0.113.20
Destination allowlist: orders-api partner
Last known change: NAT association, UDR, integration subnet or app config

Evidence required before allowlist change
DNS resolution from the application context
VNet Integration active on the right subnet
NAT Gateway associated with the expected subnet
Effective route or UDR intent understood
Source IP observed by the destination
App Service, firewall or partner logs aligned by timestamp
Configuration rollback or previous IP documented

If the team does not know which IP should be visible to the destination, it should not widen the allowlist. It should first recover the operable egress path.

Separate inbound exposure from outbound networking

Private Endpoint may exist on the same App Service for private inbound access, but it does not by itself determine the outbound IP used for Internet or partner calls. In this runbook, the question is the egress path: VNet Integration, routes, filtering, NAT and destination evidence.

text inbound-vs-outbound.txt
Do not mix
App Service Private Endpoint:
  controls private inbound access to the app
  does not prove the outbound IP used for a partner API

VNet Integration:
  places outbound app flows into an integration subnet
  exposes UDR, NSG, firewall and NAT Gateway behavior depending on the path

NAT Gateway:
  provides the public egress identity for the subnet when routing allows it
  does not apply if a UDR sends the flow to an appliance or firewall

Partner allowlist:
  must match the source IP actually observed
  should not be widened without path evidence and rollback

This avoids fixing the wrong layer. An egress IP incident is not solved by adding a Private Endpoint, and a healthy Private Endpoint does not prove that outbound traffic is controlled.

Verify VNet Integration and the real subnet

An App Service may have been moved, recreated, swapped, or attached to another integration subnet. Before looking at NAT Gateway, verify that the flow starts from the expected subnet.

bash 01-app-service-vnet-integration.sh
APP_RG="rg-prod-app"
APP_NAME="app-orders-prod"

az webapp vnet-integration list --resource-group "$APP_RG" --name "$APP_NAME" --output table

az webapp config show --resource-group "$APP_RG" --name "$APP_NAME" --query "{vnetRouteAllEnabled:vnetRouteAllEnabled, linuxFxVersion:linuxFxVersion, alwaysOn:alwaysOn}" --output table

If vnetRouteAllEnabled or the equivalent routing setting does not match the intended architecture, public destinations may not follow the integration subnet. The test must therefore state whether the partner call is expected to leave through the VNet or through native App Service egress.

Prove the DNS used by the app

Egress IP drift can hide DNS drift. If the partner hostname resolves to another region, a failover endpoint or an unexpected private address, the observed allowlist behavior can change even when NAT Gateway did not move.

text dns-egress-checks.txt
DNS checks
Resolve the FQDN from an App Service console, diagnostic endpoint or equivalent probe
Compare the resolved address with the target expected by the partner
Check custom DNS, Private Resolver, forwarder or WEBSITE_DNS_SERVER when used
Confirm that the test uses the same hostname as production
Replay after cache flush only when the incident window justifies it

Block the allowlist change when
The FQDN resolves to different targets across instances
The test uses an IP while code uses a hostname
The partner failed over regions without an updated network contract
No evidence comes from the real application context

The outbound IP only makes sense when attached to a precise destination. A partner allowlist may be correct for the old endpoint and irrelevant for the new one.

Check NAT Gateway, UDRs and firewall

NAT Gateway applies at subnet level, but only if the path reaches Internet from that subnet. A UDR toward Azure Firewall or an NVA can change the outbound identity. A missing or moved NAT association can make unexpected App Service egress IPs appear again.

bash 02-subnet-egress-shape.sh
NET_RG="rg-network-prod"
VNET="vnet-prod-spoke"
SUBNET="snet-appservice-egress-prod"

az network vnet subnet show --resource-group "$NET_RG" --vnet-name "$VNET" --name "$SUBNET" --query "{subnet:name,addressPrefix:addressPrefix,natGateway:natGateway.id,routeTable:routeTable.id,nsg:networkSecurityGroup.id}" --output json

az network route-table route list --resource-group "$NET_RG" --route-table-name "rt-appservice-egress-prod" --query "[].{name:name,prefix:addressPrefix,nextHop:nextHopType,nextHopIp:nextHopIpAddress}" --output table

The question is not only “is there a NAT Gateway?”. The team must know whether the partner flow is covered by a more specific route, whether it crosses a firewall that performs SNAT, or whether it goes directly through NAT Gateway.

Read destination and Azure evidence

The most useful proof is often on the destination side: the real source IP, timestamp, hostname, rejection code and request identifier. On the Azure side, traces must confirm the same flow.

kusto 03-egress-ip-drift-evidence.kql
let StartTime = datetime(2026-07-13T08:00:00Z);
let EndTime = datetime(2026-07-13T09:00:00Z);
let AppName = "app-orders-prod";
let PartnerHost = "api.partner.example";
AppServiceHTTPLogs
| where TimeGenerated between (StartTime .. EndTime)
| where _ResourceId has AppName
| where CsHost has PartnerHost or CsUriStem has PartnerHost
| project TimeGenerated, CsHost, CsUriStem, ScStatus, TimeTaken, UserAgent, _ResourceId
| order by TimeGenerated asc

Adapt table names to your collection pipeline. If the flow crosses Azure Firewall, correlate network or application rule logs too. If the partner can provide logs, compare timestamps and observed source IP with the expected NAT or firewall identity.

Validate without making the allowlist permanent

When an unexpected IP is observed, do not make it permanent by default. Use a short, traceable validation with an expiry and rollback.

yaml allowlist-validation-plan.yml
validation:
incident_id: egress-ip-drift-20260713-01
app: app-orders-prod
destination: api.partner.example:443
expected_source_ip: 203.0.113.20
observed_source_ip: 198.51.100.44
hypothesis:
  - app moved to another integration subnet
  - UDR sends traffic through firewall SNAT
  - NAT Gateway association changed
  - routeAll disabled for public destination
temporary_action:
  type: partner_allowlist_exception
  expires_at: 2026-07-13T12:00:00Z
  owner: platform-oncall
success:
  - destination receives traffic from documented IP
  - Azure path explains that IP
  - application probe succeeds with same hostname
rollback:
  - remove temporary allowlist entry
  - restore subnet, UDR or NAT association if drift is confirmed
  - redeploy IaC state that owns the egress path

A temporary exception may be acceptable to restore service, but it does not replace diagnosis. It should expire and point to a network fix or an architecture decision.

Decide correction, allowlist or rollback

Keep the operations decision readable. Each option needs evidence and rollback.

text egress-ip-decision.txt
Fix the Azure path
VNet Integration points to the wrong subnet
NAT Gateway is no longer associated with the expected subnet
A UDR sends the flow to a firewall that SNATs without contract
routeAll or equivalent configuration does not cover the destination
IaC can restore the expected path

Change the partner allowlist
The new source IP is intended and documented
The Azure path explains that IP
Old and new IPs have a bounded coexistence window
The partner confirms propagation
Allowlist rollback is defined

Rollback
The incident follows a subnet, NAT, UDR or firewall change
The previous path restores the expected source IP
Permanent correction needs architecture review
A temporary exception is about to expire

Block
The observed IP is proven neither by Azure nor the destination
The test does not come from the real App Service context
The team does not know whether NAT Gateway or firewall performs SNAT
The proposed allowlist adds a broad prefix without a removal date

The worst fix is a widened allowlist with no owner. It may restore the flow, but it makes the next incident harder to explain.

Conclusion

App Service egress IP drift should be handled as a production path change. Prove the integration subnet, DNS resolution, routing, NAT or firewall identity, then compare that with what the destination actually observes.

The decision is then simple: restore the Azure path, update the allowlist within a controlled window, or roll back the network change. Until the source IP is explained with evidence on both sides, the allowlist change should stay temporary or blocked.