Skip to content

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:
    • RG
    • APP_NAME
    • VNET_NAME
    • INTEGRATION_SUBNET_NAME
    • PRIVATE_ENDPOINT_SUBNET_NAME
    • CLIENT_SUBNET_NAME
    • LOCATION

When to Use

Use this guide when you need one or more of these patterns:

  1. Restrict public inbound traffic to specific source ranges.
  2. Expose the app privately over Azure Private Link.
  3. Let the app reach private resources through a delegated integration subnet.
  4. 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

Networking blade with public network access enabled, no private endpoints, virtual network integration not configured, and the outbound IPv4 and IPv6 address lists

Purpose: Show the app's inbound and outbound network configuration on one page.

Look for: Public network access and Private endpoints under inbound traffic, and Virtual network integration, Outbound DNS, and the outbound address lists under outbound traffic.

Expected result: The capture records public network access enabled without site restrictions and VNet integration not configured at capture time. It does not measure client reachability or actual outbound egress.

The Networking blade summarizes configuration, not end-to-end connectivity. Public network access: Enabled with no access restrictions records the displayed default-endpoint policy; it does not prove that any particular internet client resolves, connects, authenticates or completes a request. Private endpoints: 0 and Virtual network integration: Not configured describe separate inbound and outbound configuration. Worker outbound DNS is distinct from the client resolver used for app and SCM access. Listed outbound addresses are potential platform egress addresses, not measurements of a particular connection. After each approved change, inspect configuration and separately repeat scoped client DNS, TCP/TLS and application checks. See Private Endpoint and Access Restriction Decisions.

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 Displays selected main-site rule values. This projection omits unmatched defaults, SCM policy and inheritance; it is not a complete effective-policy or request-admission check.

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 resource update \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --resource-type "Microsoft.Web/sites" \
  --set properties.outboundVnetRouting.allTraffic=true \
  --query "properties.outboundVnetRouting" \
  --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.
--resource-group "$RG" Targets the resource group that contains the app, for each command in this block.
--name "$APP_NAME" Selects the web app, for each command in this block.
--vnet "$VNET_NAME" Names the virtual network that contains the integration subnet.
--subnet "$INTEGRATION_SUBNET_NAME" Uses the integration subnet only; do not reuse the private endpoint subnet.
az resource update Updates the site resource's routing property.
--resource-type "Microsoft.Web/sites" Targets the web app resource type.
--set properties.outboundVnetRouting.allTraffic=true Routes all outbound traffic (application and configuration) through the VNet integration. Microsoft Learn recommends this property; the legacy WEBSITE_VNET_ROUTE_ALL=1 app setting, which configures application routing only, is still supported.
--query "properties.outboundVnetRouting" Shows only the routing object, so the command does not print the whole site resource (which can contain resource IDs, hostnames and outbound addresses).
--output Selects the output format: json for the routing object, table for the integration list.
az webapp vnet-integration list Confirms the app is attached to the expected VNet and subnet.

Which routing setting

The property is a configured intent, not proof of the path. It is broader than the legacy route-all app setting: allTraffic also moves configuration traffic (image pulls, content share, backups, managed identity token acquisition) into the VNet, so make sure the integration subnet's NSGs, routes and DNS allow those dependencies first. When a site property and an app setting both exist, the site property wins. Whether DNS, the integration subnet's routes and policies, and the destination agree is verified separately; see Outbound VNet routing properties for the full property list, the legacy equivalents and what still needs DNS and network evidence.

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

Resolve the bound app and SCM hostnames from each intended client to the expected PE address. Correct DNS is necessary but insufficient: separately validate route, TCP/TLS, response origin and authorized application/deployment behavior.

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 policy results: an approved default-path source is admitted by the applicable rule; an unapproved source is rejected when the effective policy denies it. An admitted request returns 200 only if this app implements /health and its authentication and application behavior permit that response. Attribute a 403 to access restrictions only with correlated policy and response-origin evidence.

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" Records this client's DNS chain and final address for comparison with the intended PE.
curl --write-out "%{http_code}" Records an HTTP status on this client path; status alone does not establish PE routing or business success.

Expected results: the final DNS address matches the intended PE, connection/TLS evidence establishes the intended endpoint, and the application returns its predeclared expected response. A privatelink CNAME alone is insufficient; HTTP 200 is not a generic PE requirement.

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 -->|Predeclared expected response| Done[Expected response observed on this path]

Layered checks:

  1. DNS resolution path
  2. TCP reachability
  3. 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.
HTTP 403 from a public client after PE rollout Default-endpoint app access, site rules, Microsoft.Web service-endpoint incompatibility, proxy, authentication or application rejection remain alternatives Establish the actual path and response origin; inspect raw publicNetworkAccess, applicable main/SCM policy and source context before changing configuration.
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

Removing a PE is an approved topology change, not a neutral connectivity test. First record app access, DNS and operator access dependencies; a null publicNetworkAccess setting defaults to enabled when no PE exists. Validate the resulting main/SCM positive and negative controls after removal.

To remove private inbound access:

az network private-endpoint delete \
  --resource-group "$RG" \
  --name "pe-$APP_NAME"
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

DNS, route ownership and subnet design drift are frequently worth checking before assuming the App Service resource itself is at fault; confirm with evidence from the actual client path.

Language-Specific Details

For language-specific operational guidance, see:

See Also

Sources