Networking Operations¶
Operate Azure App Service networking by treating inbound and outbound paths separately: use access restrictions or private endpoints for inbound reachability, and use virtual network integration for outbound access to private dependencies.
Prerequisites¶
- Existing Web App and App Service Plan in a supported dedicated tier
- Existing virtual network with separate subnets for:
- private endpoint
- VNet integration
- client test host (recommended)
- RBAC permissions for App Service, Virtual Network, Private Endpoint, and Private DNS resources
- Variables set:
RGAPP_NAMEVNET_NAMEINTEGRATION_SUBNET_NAMEPRIVATE_ENDPOINT_SUBNET_NAMECLIENT_SUBNET_NAMELOCATION
When to Use¶
Use this guide when you need one or more of these patterns:
- Restrict public inbound traffic to specific source ranges.
- Expose the app privately over Azure Private Link.
- Let the app reach private resources through a delegated integration subnet.
- Validate DNS, routing, and reachability after networking changes.
Procedure¶
flowchart TD
Client[Client] --> Inbound{Inbound path}
Inbound --> Access[Access restrictions]
Inbound --> PrivateEndpoint[Private endpoint]
Access --> App[App Service app]
PrivateEndpoint --> App
App --> Outbound{Outbound path}
Outbound --> VNetIntegration[VNet integration]
VNetIntegration --> PrivateDeps[Private dependencies]
VNetIntegration --> NatOrFirewall[NAT gateway or firewall optional] Combined Architecture (Private Inbound + Private Outbound)¶
flowchart TD
InternalClient[Client in connected network] --> PublicDns[Public DNS for app hostname]
PublicDns --> PrivateDns[Private DNS zone for privatelink.azurewebsites.net]
PrivateDns --> PrivateEndpoint[Private endpoint in dedicated subnet]
PrivateEndpoint --> App[App Service app]
App --> VNetIntegration[Delegated integration subnet]
VNetIntegration --> PrivateResource[Private dependency]
VNetIntegration --> OptionalNat[Optional NAT gateway or firewall] Portal view: Networking blade¶

The Networking blade is the single Portal surface that ties together every operation in this guide. Reading top to bottom against the inbound/outbound flowchart above: Public network access: Enabled with no access restrictions proves the app is reachable from the entire internet and is exactly the gap the access-restriction rules in step 1 below close; Private endpoints: 0 is the surface step 3 changes; and Virtual network integration: Not configured is what step 2 connects. The Outbound DNS: Default (Azure-provided) row controls the resolver the App Service worker uses for its own outbound calls — separately, step 4 below configures the privatelink.azurewebsites.net private DNS zone so clients in your network resolve app-name.azurewebsites.net to the private endpoint IP instead of the public hostname. The long Outbound IPv4 addresses list is the pool of platform-assigned egress IPs the app can use before VNet integration takes over — these are documented but not guaranteed stable, which is why step 5 covers NAT gateway egress for deterministic outbound paths. Return to this blade after each step to confirm the corresponding row no longer reads "Not configured" or "default behavior".
1. Configure Inbound Access Restrictions¶
Allow approved public sources first, then add a deny fallback.
az webapp config access-restriction add \
--resource-group "$RG" \
--name "$APP_NAME" \
--rule-name "AllowCorp" \
--action Allow \
--ip-address "203.0.113.0/24" \
--priority 100 \
--output json
az webapp config access-restriction add \
--resource-group "$RG" \
--name "$APP_NAME" \
--rule-name "DenyAll" \
--action Deny \
--ip-address "0.0.0.0/0" \
--priority 2147483647 \
--output json
az webapp config access-restriction show \
--resource-group "$RG" \
--name "$APP_NAME" \
--query "ipSecurityRestrictions[].{name:name,action:action,ip:ipAddress,priority:priority}" \
--output table
| Command/Parameter | Purpose |
|---|---|
az webapp config access-restriction add | Adds ordered allow or deny rules on the App Service front end. |
--ip-address "203.0.113.0/24" | Uses a documentation-only example CIDR for allowed corporate ingress. |
--priority 2147483647 | Places the deny rule at the lowest precedence so explicit allows are evaluated first. |
az webapp config access-restriction show | Verifies effective rule order and rule values. |
2. Enable VNet Integration for Outbound Traffic¶
Use a dedicated delegated subnet for outbound connectivity. Private endpoint and VNet integration must use different subnets.
az webapp vnet-integration add \
--resource-group "$RG" \
--name "$APP_NAME" \
--vnet "$VNET_NAME" \
--subnet "$INTEGRATION_SUBNET_NAME" \
--output json
az webapp config appsettings set \
--resource-group "$RG" \
--name "$APP_NAME" \
--settings "WEBSITE_VNET_ROUTE_ALL=1" \
--output json
az webapp vnet-integration list \
--resource-group "$RG" \
--name "$APP_NAME" \
--output table
| Command/Parameter | Purpose |
|---|---|
az webapp vnet-integration add | Connects the app to the delegated subnet for outbound private connectivity. |
--subnet "$INTEGRATION_SUBNET_NAME" | Uses the integration subnet only; do not reuse the private endpoint subnet. |
WEBSITE_VNET_ROUTE_ALL=1 | Forces all outbound app traffic into the VNet integration path. |
az webapp vnet-integration list | Confirms the app is attached to the expected VNet and subnet. |
Subnet sizing
Microsoft Learn recommends planning enough IP space for scale operations and platform upgrades. For a single multitenant App Service plan, /26 gives the safest headroom; /28 is the minimum supported size when the subnet already exists.
3. Create a Private Endpoint for Inbound Private Access¶
The private endpoint handles inbound traffic only. It doesn't replace VNet integration for outbound traffic.
APP_ID="$(az webapp show \
--resource-group "$RG" \
--name "$APP_NAME" \
--query id \
--output tsv)"
az network private-endpoint create \
--resource-group "$RG" \
--name "pe-$APP_NAME" \
--vnet-name "$VNET_NAME" \
--subnet "$PRIVATE_ENDPOINT_SUBNET_NAME" \
--private-connection-resource-id "$APP_ID" \
--group-id "sites" \
--connection-name "pec-$APP_NAME" \
--output json
az network private-endpoint show \
--resource-group "$RG" \
--name "pe-$APP_NAME" \
--query "{state:provisioningState,connections:privateLinkServiceConnections[].privateLinkServiceConnectionState.status}" \
--output json
| Command/Parameter | Purpose |
|---|---|
az webapp show --query id | Retrieves the App Service resource ID required by the private endpoint create command. |
az network private-endpoint create | Creates the App Service private endpoint in the selected subnet. |
--group-id "sites" | Uses the correct App Service subresource for the production slot. |
az network private-endpoint show | Verifies provisioning state and private link connection approval status. |
Private endpoint scope
Access restriction rules are not evaluated for traffic that arrives through the private endpoint. If you want a true private-only pattern, disable public network access separately and validate private DNS resolution.
4. Configure Private DNS Resolution¶
For App Service private endpoints, clients should resolve app-name.azurewebsites.net through the privatelink.azurewebsites.net private DNS zone. Use a private DNS zone group so the required records are created automatically.
az network private-dns zone create \
--resource-group "$RG" \
--name "privatelink.azurewebsites.net" \
--output json
az network private-dns link vnet create \
--resource-group "$RG" \
--zone-name "privatelink.azurewebsites.net" \
--name "link-$VNET_NAME" \
--virtual-network "$VNET_NAME" \
--registration-enabled false \
--output json
az network private-endpoint dns-zone-group create \
--resource-group "$RG" \
--endpoint-name "pe-$APP_NAME" \
--name "default" \
--private-dns-zone "privatelink.azurewebsites.net" \
--zone-name "privatelink.azurewebsites.net" \
--output json
| Command/Parameter | Purpose |
|---|---|
az network private-dns zone create | Creates the App Service private DNS zone. |
az network private-dns link vnet create | Links the zone to the client VNet so clients can resolve the private endpoint path. |
az network private-endpoint dns-zone-group create | Automatically creates the app and SCM DNS records for the private endpoint. |
--registration-enabled false | Prevents VM autoregistration, which isn't needed for this zone. |
DNS is the usual failure point
A private endpoint deployment isn't operational until clients in the linked network resolve the app hostname to the private endpoint address path instead of the public internet path.
5. Review Outbound Egress Expectations¶
App Service outbound IP lists are potential addresses. When you use route-all with a NAT gateway or firewall, validate the actual egress path from the integrated subnet.
az webapp show \
--resource-group "$RG" \
--name "$APP_NAME" \
--query "{outbound:outboundIpAddresses,possible:possibleOutboundIpAddresses}" \
--output json
az network vnet subnet show \
--resource-group "$RG" \
--vnet-name "$VNET_NAME" \
--name "$INTEGRATION_SUBNET_NAME" \
--query "natGateway.id" \
--output tsv
| Command/Parameter | Purpose |
|---|---|
az webapp show | Displays documented outbound and possible outbound IP sets for the app. |
az network vnet subnet show | Confirms whether the integration subnet is attached to a NAT gateway. |
natGateway.id | Returns the NAT gateway resource ID when deterministic outbound egress is configured. |
Verification¶
Verify Public Access Restriction Behavior¶
Run from an allowed and then a blocked public source.
curl --silent --output /dev/null --write-out "%{http_code}" \
"https://$APP_NAME.azurewebsites.net/health"
| Command/Parameter | Purpose |
|---|---|
curl --write-out "%{http_code}" | Confirms whether the app returns the expected status code without printing the full body. |
https://$APP_NAME.azurewebsites.net/health | Uses a simple health endpoint for reachability validation. |
Expected results:
- allowed public source:
200 - blocked public source:
403
Verify Private Endpoint Reachability from a Connected Network¶
You need a client inside the linked VNet or a connected network. A VM is the most common test client. Azure Bastion is only an access method to that VM; it doesn't replace the VM or other in-network client.
Supported client patterns:
- VM in the VNet, accessed through Azure Bastion
- VM in the VNet, accessed through VPN or ExpressRoute
- Existing workstation in a connected network with DNS forwarding to Azure private DNS
Option A: Create a Temporary Test VM and Access It with Bastion¶
Use a client subnet, not the private endpoint subnet, for the VM. If you need to create AzureBastionSubnet, choose an unused /26 or larger prefix from the existing VNet address space.
az network vnet subnet create \
--resource-group "$RG" \
--vnet-name "$VNET_NAME" \
--name "AzureBastionSubnet" \
--address-prefixes "<unused-vnet-prefix-for-bastion-subnet>"
az network public-ip create \
--resource-group "$RG" \
--name "pip-bastion" \
--sku "Standard" \
--location "$LOCATION"
az network bastion create \
--resource-group "$RG" \
--name "bastion-$APP_NAME" \
--vnet-name "$VNET_NAME" \
--public-ip-address "pip-bastion" \
--location "$LOCATION" \
--sku "Standard"
az vm create \
--resource-group "$RG" \
--name "vm-jumpbox" \
--image "Ubuntu2404" \
--size "Standard_B1s" \
--vnet-name "$VNET_NAME" \
--subnet "$CLIENT_SUBNET_NAME" \
--admin-username "azureuser" \
--generate-ssh-keys \
--public-ip-address ""
| Command/Parameter | Purpose |
|---|---|
AzureBastionSubnet | Required subnet name for Azure Bastion. |
--address-prefixes "<unused-vnet-prefix-for-bastion-subnet>" | Placeholder for an unused /26 or larger prefix from the existing VNet address space. |
az network bastion create | Provisions Bastion so you can reach the test VM without giving it a public IP. |
--sku "Standard" | Uses the SKU required for native-client SSH with az network bastion ssh. |
--subnet "$CLIENT_SUBNET_NAME" | Places the VM in a dedicated client subnet instead of the private endpoint subnet. |
--public-ip-address "" | Creates the VM without a public IP. |
Connect to the VM:
az network bastion ssh \
--resource-group "$RG" \
--name "bastion-$APP_NAME" \
--target-resource-id "$(az vm show --resource-group "$RG" --name "vm-jumpbox" --query id --output tsv)" \
--auth-type "ssh-key" \
--username "azureuser" \
--ssh-key "$HOME/.ssh/id_rsa"
| Command/Parameter | Purpose |
|---|---|
az network bastion ssh | Opens an SSH session to the VM through Bastion. |
--target-resource-id | Resolves the VM resource ID inline so the session targets the correct VM. |
--ssh-key "$HOME/.ssh/id_rsa" | Uses an explicit home-directory path so shell expansion is unambiguous. |
Once connected to the VM, validate DNS and HTTP reachability:
nslookup "$APP_NAME.azurewebsites.net"
curl --silent --output /dev/null --write-out "%{http_code}" \
"https://$APP_NAME.azurewebsites.net/health"
| Command/Parameter | Purpose |
|---|---|
nslookup "$APP_NAME.azurewebsites.net" | Confirms the hostname resolves through the private endpoint DNS chain from the client network. |
curl --write-out "%{http_code}" | Confirms the private endpoint can serve the app successfully. |
Expected results:
- DNS answer includes the
privatelink.azurewebsites.netpath. - HTTP response is
200from the in-network client.
Option B: Use an Existing Connected Client Instead of Creating a VM¶
If you already have a client in the linked VNet, a peered VNet, or an on-premises network connected through VPN or ExpressRoute, run the same nslookup and curl checks there. This is often preferable to creating temporary infrastructure.
Cleanup temporary test resources
Delete short-lived validation resources after testing.
az vm delete --resource-group "$RG" --name "vm-jumpbox" --yes
az network bastion delete --resource-group "$RG" --name "bastion-$APP_NAME"
az network public-ip delete --resource-group "$RG" --name "pip-bastion"
| Command/Parameter | Purpose |
|---|---|
az vm delete | Removes the temporary test VM. |
az network bastion delete | Removes the Bastion host when you no longer need private access testing. |
az network public-ip delete | Removes the public IP reserved for Bastion. |
Verify Outbound Private Dependency Reachability¶
From the Kudu or SSH console of the app, validate DNS and port reachability to the private dependency.
nameresolver "your-private-resource.contoso.local"
tcpping "your-private-resource.contoso.local" 443
| Command/Parameter | Purpose |
|---|---|
nameresolver | Resolves the dependency hostname from the App Service worker context. |
tcpping | Tests TCP connectivity from the app worker to the dependency endpoint and port. |
Network Debugging Checklist¶
flowchart TD
Start[Connectivity issue] --> DNS[1. Validate DNS]
DNS -->|Public path or NXDOMAIN| DNSFix[Fix private DNS zone, records, or VNet link]
DNS -->|Private path resolves| TCP[2. Validate TCP reachability]
TCP -->|Timeout| RouteFix[Check NSG, UDR, firewall, peering, or VPN path]
TCP -->|Port reachable| HTTP[3. Validate application response]
HTTP -->|403 or 5xx| AppFix[Check app auth, access policy, or app health]
HTTP -->|200| Done[Connection verified] Layered checks:
- DNS resolution path
- TCP reachability
- Application response
Common Failures and Fixes¶
| Symptom | Likely Cause | Fix |
|---|---|---|
NXDOMAIN or public resolution path | Private DNS zone missing, unlinked, or not forwarded | Link the VNet to privatelink.azurewebsites.net and validate DNS forwarding. |
403 from a public client after private endpoint rollout | Public network access still enabled but caller isn't allowed by access restrictions | Decide whether the app should stay public, then update restriction rules or disable public access. |
| Private endpoint works but app can't reach backend | VNet integration missing or routed incorrectly | Validate integration subnet, route-all setting, and NSG or UDR behavior. |
| Intermittent outbound failures | NAT/SNAT assumptions don't match actual egress path | Validate NAT gateway or firewall path on the integration subnet. |
| Kudu or SCM hostname doesn't resolve privately | DNS zone group or SCM record missing | Recreate or validate the private endpoint DNS zone group. |
Rollback / Troubleshooting¶
To remove private inbound access:
| Command/Parameter | Purpose |
|---|---|
az network private-endpoint delete | Removes the App Service private endpoint resource. |
To remove outbound VNet integration:
az webapp vnet-integration remove \
--resource-group "$RG" \
--name "$APP_NAME" \
--vnet "$VNET_NAME"
| Command/Parameter | Purpose |
|---|---|
az webapp vnet-integration remove | Disconnects the app from the virtual network integration path. |
Advanced Topics¶
Zero Public Ingress Pattern¶
Use this combination for private-only access:
- Private endpoint for inbound traffic
- Public network access disabled after validation
- Private DNS for app and SCM hostnames
- VNet integration for outbound private dependencies
Hub-and-Spoke Governance¶
For larger estates:
- centralize DNS forwarding and private DNS ownership
- document which team owns NSGs, UDRs, and firewall policy
- standardize subnet sizing for integration and private endpoint patterns
Change Management¶
- validate DNS before changing traffic policy
- stage NSG and route updates separately from app changes
- keep a repeatable in-network test path for post-change verification
Operational reality
Most App Service networking incidents are caused by DNS, route ownership, or subnet design drift rather than by the App Service resource itself.
Language-Specific Details¶
For language-specific operational guidance, see:
See Also¶
- Operations Index
- Security
- Health and Recovery
- App Service networking features (Microsoft Learn)
- Use private endpoints for apps (Microsoft Learn)
- Integrate your app with an Azure virtual network (Microsoft Learn)
- Configure Bastion for native client connections (Microsoft Learn)
- About Azure Bastion configuration settings (Microsoft Learn)