Skip to content

Networking Best Practices

Networking design in Azure App Service is primarily about controlling direction and trust boundaries: private inbound paths, predictable outbound paths, and reliable DNS behavior across those paths.

Why This Matters

Networking is the first control plane an App Service workload exposes to the outside world, and it is also the layer that most often fails silently in production. Getting these decisions right up front is materially cheaper than retrofitting them:

  • Outbound IPs can change under scale operations, region moves, or SKU changes — hard-coded allowlists break with no explicit warning.
  • Private Endpoint and VNet Integration solve different problems (inbound privacy vs outbound reachability); confusing them leads to either data-plane exposure or broken egress.
  • SNAT port exhaustion presents as intermittent connectivity issues that are indistinguishable from remote-service faults without the right diagnostic signals in place.
  • DNS resolution paths differ across shared-plan, VNet-integrated, and Private Endpoint topologies; a stale mental model causes reproducible-but-unexplained lookup failures.

This document consolidates the concrete design and validation practices that keep those failure modes from becoming incidents.

Prerequisites

  • Existing Web App and App Service Plan
  • Existing virtual network and required subnets
  • Permissions to manage App Service, networking, and private DNS resources
  • Variables set:
    • RG
    • APP_NAME
    • VNET_NAME
    • INTEGRATION_SUBNET_NAME
    • PRIVATE_ENDPOINT_SUBNET_NAME

Networking goals for production

Use App Service networking to achieve four outcomes:

  1. Inbound exposure is intentionally restricted.
  2. Outbound traffic to private dependencies is deterministic.
  3. SNAT behavior is managed under concurrency.
  4. DNS resolution aligns with your private topology.
graph TD
    Internet[Internet Users] --> WAF[WAF or Reverse Proxy]
    WAF --> PE[Private Endpoint for App Service]

    subgraph VNet[Azure Virtual Network]
        subgraph PE_SUBNET[Private Endpoint Subnet]
            PE
        end
        subgraph APP_SUBNET[VNet Integration Subnet]
            VNETINT[VNet Integration]
        end
        subgraph DEP_SUBNET[Private Dependency Subnet]
            SQL[(Azure SQL Private Endpoint)]
            REDIS[(Azure Cache Private Endpoint)]
            API[Internal API]
        end
        DNS[Private DNS Zones]
    end

    App[Azure App Service App] --> VNETINT
    VNETINT --> SQL
    VNETINT --> REDIS
    VNETINT --> API
    App --> DNS
    DNS --> PE
    DNS --> SQL
    DNS --> REDIS

1) Use VNet Integration for outbound traffic

VNet Integration is the primary App Service feature for private outbound connectivity.

Use it when your app must reach:

  • Private endpoints for PaaS dependencies
  • Internal services reachable through peering/express routes
  • Internal DNS zones and split-horizon naming schemes

Configure integration:

az webapp vnet-integration add \
  --resource-group $RG \
  --name $APP_NAME \
  --vnet $VNET_NAME \
  --subnet $INTEGRATION_SUBNET_NAME \
  --output json
Flag Description
--resource-group $RG Targets the resource group that contains the web app.
--name $APP_NAME Selects the App Service app to connect to the VNet.
--vnet $VNET_NAME Chooses the virtual network that provides private outbound reachability.
--subnet $INTEGRATION_SUBNET_NAME Uses the delegated integration subnet that App Service will attach for outbound traffic.
--output json Returns the integration result as JSON so you can confirm the attached subnet details.

Route all app outbound through VNet path where required:

az webapp config appsettings set \
  --resource-group $RG \
  --name $APP_NAME \
  --settings WEBSITE_VNET_ROUTE_ALL=1 \
  --output json
Flag Description
--resource-group $RG Targets the resource group that contains the app.
--name $APP_NAME Selects the web app whose runtime settings will be updated.
--settings WEBSITE_VNET_ROUTE_ALL=1 Writes the setting that forces outbound app traffic onto the VNet-integrated path.
--output json Returns the updated app settings payload in JSON format.

Validate integration state:

az webapp vnet-integration list \
  --resource-group $RG \
  --name $APP_NAME \
  --output table
Flag Description
--resource-group $RG Scopes the query to the resource group that contains the app.
--name $APP_NAME Lists the VNet integration entries attached to this web app.
--output table Shows the integration subnet and VNet details in a quick-readable table.

Outbound only

VNet Integration controls outbound connectivity. It does not make inbound access private by itself.

2) Use Private Endpoint for inbound privacy

Private Endpoint is the standard pattern for private inbound access to an App Service app.

Use it when:

  • App must not be publicly reachable
  • Access should originate only from approved VNets
  • You need private IP resolution for app endpoints

Create private endpoint:

APP_RESOURCE_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_RESOURCE_ID \
  --group-id sites \
  --connection-name "pec-$APP_NAME" \
  --output json
Command Description
APP_RESOURCE_ID=$(az webapp show --resource-group $RG --name $APP_NAME --query id --output tsv) Reads the web app ARM resource ID into a shell variable so the Private Endpoint can target that app resource.
az network private-endpoint create ... Creates a Private Endpoint in the specified subnet and connects it to the App Service sites private link group.

Check connection status:

az network private-endpoint show \
  --resource-group $RG \
  --name "pe-$APP_NAME" \
  --query "{state:provisioningState,connection:privateLinkServiceConnections[0].privateLinkServiceConnectionState.status}" \
  --output json
Flag Description
--resource-group $RG Looks up the Private Endpoint in the given resource group.
--name "pe-$APP_NAME" Selects the specific Private Endpoint created for the app.
--query "{state:provisioningState,connection:privateLinkServiceConnections[0].privateLinkServiceConnectionState.status}" Returns the endpoint provisioning state plus the first private link connection approval status.
--output json Formats the reduced status payload as JSON.

3) Design for SNAT port efficiency

SNAT exhaustion is a frequent cause of intermittent outbound failures under concurrency.

Use these patterns to reduce SNAT pressure:

  • Connection pooling for database and HTTP clients
  • Async I/O patterns to avoid thread starvation and long-held sockets
  • Reuse HTTP clients instead of creating per-request clients
  • Bounded retries with jitter to avoid retry storms
  • Short dependency timeouts to free sockets faster

Practical implementation guidance:

Dependency type SNAT-friendly pattern Anti-pattern
HTTP APIs Shared client instance with keep-alive New client object per request
SQL/DB Runtime-level connection pool Open/close connection on every small operation
Cache Reused multiplexer/client Frequent reconnect loops
Messaging Persistent channels where supported Burst connect/disconnect cycles

SNAT symptoms are often misleading

SNAT exhaustion can look like random timeouts, dependency flakiness, or transient DNS failures. Always inspect connection lifecycle and concurrency patterns before blaming downstream services.

4) Own DNS behavior in private topologies

Private networking fails most often at DNS, not at routing.

For App Service private endpoint scenarios:

  1. Configure privatelink.azurewebsites.net private DNS zone.
  2. Link the zone to the correct VNets.
  3. Ensure clients resolve app hostname to private IP.
  4. Validate dependency hostnames resolve to private addresses where expected.

Create and link private DNS zone:

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
Command Description
az network private-dns zone create ... Creates the Private DNS zone that maps App Service private endpoint names under privatelink.azurewebsites.net.
az network private-dns link vnet create ... Links that Private DNS zone to the VNet so clients in the VNet can resolve the app hostname to the private IP.

Validate DNS from private network context:

nslookup "$APP_NAME.azurewebsites.net"

Expected: private IP address in your VNet range.

Do not mix unresolved DNS ownership

If platform, network, and app teams each assume another team owns DNS, incidents become long and repetitive. Assign explicit DNS ownership and validation steps.

5) Hybrid Connections vs VNet Integration

VNet Integration provides outbound private connectivity. Private Endpoint provides inbound private access. They solve different directions of traffic flow.

Decision area Hybrid Connections VNet Integration
Connectivity model App-level relay to specific host:port endpoints Network-level integration to a delegated subnet
Scope Targeted dependency access Broad private network access
Operational complexity Simpler for limited cases Better for comprehensive private dependency strategy
Protocol flexibility Host/port specific constraints Works with broader private routing patterns
Typical use case Few legacy endpoints without full VNet adoption Standard enterprise private architecture

Decision guideline:

  • Choose Hybrid Connections for a narrow, transitional requirement.
  • Choose VNet Integration for strategic private networking.
flowchart TD
    A[Need private outbound connectivity?] --> B{Only a few specific host:port endpoints?}
    B -->|Yes| C[Consider Hybrid Connections]
    B -->|No| D[Use VNet Integration]
    C --> E{Long-term enterprise private architecture?}
    E -->|Yes| D
    E -->|No| F[Keep Hybrid Connections with explicit limits]
    D --> G[Plan DNS, routes, and subnet capacity]

6) Inbound control layering

Even with private endpoints, define layered inbound control:

  • Edge control (WAF/reverse proxy)
  • App access restrictions for explicit allow/deny where applicable
  • Private endpoint approval workflow
  • Monitoring for unauthorized or unexpected requests

Example access restriction rule:

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
Flag Description
--resource-group $RG Targets the resource group that contains the app.
--name $APP_NAME Applies the access restriction to this web app.
--rule-name AllowCorp Names the rule so it can be reviewed or removed later.
--action Allow Permits requests that match the specified source range.
--ip-address 203.0.113.0/24 Limits the allow rule to that corporate CIDR block.
--priority 100 Evaluates this rule before the default catch-all rule because lower numbers run first.
--output json Returns the created rule details in JSON.

Portal view: Access Restrictions blade

Access Restrictions blade with public network access on its default enabled behavior, unmatched rule action set to Allow, and only the default Allow all rule

Purpose: Show the inbound access state before any access restriction is added.

Look for: Public network access shows Enabled (using default behavior), Unmatched rule action is Allow, and the only rule is Allow all with priority 2147483647.

Expected result: With no custom rules, the displayed default-endpoint policy admits any source until you add rules or disable public network access. This is a configuration reading, not a measured reachability test.

The Access Restrictions blade is where the layered inbound model described above is enforced. The visible default state shows the two most important behaviors this guide warns against assuming away: Public network access is Enabled (using default behavior), which is the portal wording for an unset property whose documented default is enabled when no private endpoint exists, and the implicit Allow all rule at priority 2147483647 with Unmatched rule action: Allow means that without any explicit rules the main-site policy admits every source on the default endpoint. This displays configuration; it does not show whether any client can reach the app. The Main site and Advanced tool site tabs separate runtime traffic from SCM/Kudu management traffic — both must be hardened. After running the az webapp config access-restriction add command above with priority 100, the new AllowCorp rule should appear above the default Allow all row, and the Unmatched rule action should be flipped to Deny to convert the implicit allow-by-default posture into an explicit deny-by-default posture.

7) Troubleshooting patterns to pre-plan

Include these commands in runbooks:

az webapp show \
  --resource-group $RG \
  --name $APP_NAME \
  --query "{defaultHostName:defaultHostName,outboundIpAddresses:outboundIpAddresses}" \
  --output json

az network private-endpoint list \
  --resource-group $RG \
  --output table

az network private-dns record-set a list \
  --resource-group $RG \
  --zone-name privatelink.azurewebsites.net \
  --output table
Command Description
az webapp show ... --query "{defaultHostName:defaultHostName,outboundIpAddresses:outboundIpAddresses}" Shows the app hostname and its current outbound IP set for quick incident triage.
az network private-endpoint list --resource-group $RG --output table Lists Private Endpoints in the resource group so you can confirm the app endpoint exists and identify its name.
az network private-dns record-set a list ... Lists A records in the App Service private DNS zone so you can verify hostname-to-private-IP mappings.

8) Manage IP address changes proactively

App Service IP addresses are not static. Treating them as fixed causes allowlist drift, broken firewall rules, and silent connectivity failures after scale events.

Outbound IP allowlist strategy

Two CLI fields exist for outbound IPs — use the right one:

Field What it represents When to use
outboundIpAddresses IPs currently active Read-only diagnostics
possibleOutboundIpAddresses All IPs the app may ever use on this plan Firewall allowlists — always use this

Query both to understand current vs. potential exposure:

az webapp show \
    --resource-group "$RG" \
    --name "$APP_NAME" \
    --query "{active:outboundIpAddresses,possible:possibleOutboundIpAddresses}" \
    --output json
Flag Description
--resource-group "$RG" Queries the web app from the specified resource group.
--name "$APP_NAME" Selects the app whose outbound IP ranges you need to review.
--query "{active:outboundIpAddresses,possible:possibleOutboundIpAddresses}" Returns the current outbound IPs and the full possible outbound IP set in one object.
--output json Preserves both IP lists as JSON for allowlist review or automation.

Example output (PII masked):

Output field Meaning Operational use
active Currently in-use outbound IPs on this plan Diagnostic snapshot only — do not allowlist
possible Full candidate set the plan could rotate through Union of active+possible is the correct allowlist
{
  "active": "<13 outbound IPv4 addresses; run the command above to retrieve current values>",
  "possible": "<31 additional outbound IPv4 addresses; the union of active + possible is the full allowlist candidate set>"
}

Events that change outbound IPs

  • Scaling the App Service Plan up or down (SKU change)
  • Moving the app to a different region or scale unit
  • Enabling or disabling VNet Integration
  • Platform-side infrastructure updates

After any of these events, re-query possibleOutboundIpAddresses and update downstream allowlists before traffic is restored.

Outbound IP behavior under VNet Integration

With route-all enabled (formerly WEBSITE_VNET_ROUTE_ALL=1, now exposed as a platform setting through the portal or CLI), all outbound traffic is routed through the VNet. The source IP remains one of the app's standard outbound IPs unless the integration subnet uses a NAT Gateway or forces traffic through a firewall or NVA, in which case the egress IP becomes that device's IP.

Verify after enabling:

# Confirm VNet integration is active
az webapp vnet-integration list \
    --resource-group "$RG" \
    --name "$APP_NAME" \
    --output table

# Confirm route-all setting
az webapp config appsettings list \
    --resource-group "$RG" \
    --name "$APP_NAME" \
    --query "[?name=='WEBSITE_VNET_ROUTE_ALL']" \
    --output table
Command Description
az webapp vnet-integration list --resource-group "$RG" --name "$APP_NAME" --output table Confirms that the app is attached to the expected integration subnet and VNet.
az webapp config appsettings list --resource-group "$RG" --name "$APP_NAME" --query "[?name=='WEBSITE_VNET_ROUTE_ALL']" --output table Filters the app settings list to the WEBSITE_VNET_ROUTE_ALL entry so you can verify whether route-all is enabled.
Location       Name              ResourceGroup     VnetResourceId
-------------  ----------------  ----------------  -------------------------------------------------
Korea Central  snet-integration  rg-<masked>       .../virtualNetworks/vnet-<masked>/subnets/snet-integration
Name                    Value    SlotSetting
----------------------  -------  -------------
WEBSITE_VNET_ROUTE_ALL  1        False

Update downstream allowlists after enabling VNet Integration

Route-all changes the path of outbound traffic, but not necessarily the observed source IP. Downstream services will continue to see one of the app's standard outbound IPs unless the integration subnet uses a NAT Gateway or routes traffic through a firewall or NVA. Update allowlists to reflect the actual egress design before enabling route-all in production.

Inbound IP and Private Endpoint behavior

Creating a Private Endpoint assigns a private IP from the endpoint subnet. Whether the default (public) inbound endpoint stays reachable depends on the raw publicNetworkAccess value, whether a private endpoint exists, and the main and SCM access restrictions: when the property is unset and a private endpoint exists the documented default is disabled, while an explicit Enabled value keeps it on. For private-only inbound access, set and verify publicNetworkAccess deliberately. Site access restrictions are not evaluated for private endpoint traffic, so they constrain only the default endpoint while it stays enabled. See Private Endpoint and Access Restriction Decisions.

Query the current inbound IP:

az webapp show \
    --resource-group "$RG" \
    --name "$APP_NAME" \
    --query "inboundIpAddress" \
    --output tsv
Flag Description
--resource-group "$RG" Looks up the app in the specified resource group.
--name "$APP_NAME" Selects the web app whose current public inbound IP you want to inspect.
--query "inboundIpAddress" Returns only the app's inbound IP address field.
--output tsv Prints the IP as plain text so it can be copied into tests or notes.

Query the Private Endpoint's assigned private IP:

az network private-endpoint show \
    --resource-group "$RG" \
    --name "pe-$APP_NAME" \
    --query "customDnsConfigs[0].ipAddresses" \
    --output json
Flag Description
--resource-group "$RG" Reads the Private Endpoint from the given resource group.
--name "pe-$APP_NAME" Selects the app's Private Endpoint resource.
--query "customDnsConfigs[0].ipAddresses" Returns the IP list from the first custom DNS config entry attached to the Private Endpoint.
--output json Keeps the returned private IP list in JSON array form.

Example output:

[
  "192.0.2.4"
]

Validate that DNS resolves to the private IP from within the VNet:

nslookup "$APP_NAME.azurewebsites.net"

When run from a VM inside the VNet with the private DNS zone configured, the response resolves to the Private Endpoint IP (for example, 192.0.2.4) instead of the public IP. The Private Endpoint IP was confirmed via az network private-endpoint show (koreacentral, 2026-05-01) and sanitized for publication.

A Private Endpoint does not by itself prove public access is closed

A Private Endpoint adds a private inbound path. Default-endpoint access depends on the raw publicNetworkAccess value, whether a private endpoint exists, and the main and SCM access restrictions: when the property is unset and a private endpoint exists the documented default is disabled, while an explicit Enabled value keeps it on. Complete the sequence:

  1. Create the Private Endpoint.
  2. Verify DNS resolves to the private IP from the target VNet.
  3. Record the raw publicNetworkAccess value and set it deliberately (Disabled for private-only access). If the default endpoint must stay enabled, add access restrictions that constrain it.
  4. Validate main and SCM separately, with a negative control against the default endpoint.

If the default endpoint stays enabled, a rule like the following constrains only default-endpoint traffic; site rules are not evaluated for private endpoint traffic.

az webapp config access-restriction add \
    --resource-group "$RG" \
    --name "$APP_NAME" \
    --rule-name "DenyPublic" \
    --action Deny \
    --ip-address 0.0.0.0/0 \
    --priority 65000 \
    --output json
Flag Description
--resource-group "$RG" Targets the resource group that contains the app.
--name "$APP_NAME" Applies the deny rule to this web app.
--rule-name "DenyPublic" Names the rule that blocks public sources.
--action Deny Rejects traffic that matches the specified source range.
--ip-address 0.0.0.0/0 Matches all IPv4 sources on the default endpoint. IPv6 and the separate SCM site policy need their own rules or inheritance.
--priority 65000 Places the deny rule late in the list, so earlier rules (including Allow rules) are evaluated first and a matching earlier Allow still admits the request.
--output json Returns the created restriction rule in JSON.

9) Choose the right outbound path to Azure Storage

When your app connects to Azure Storage (Blob, Files, Queue, Table) and the storage account has a firewall, pick one of three supported patterns and configure all four connectivity layers — DNS, routing, storage firewall, and data-plane authorization — deliberately.

Pattern When to use Configuration
Public endpoint (no firewall) Non-sensitive data, simplest setup Storage default-action Allow; no VNet integration required
Service endpoint Same-VNet apps, no private DNS overhead VNet integration + Microsoft.Storage service endpoint + storage network rule for the subnet
Private endpoint Strict private-only access, cross-region, hub-spoke VNet integration + private endpoint + privatelink.<service>.core.windows.net private DNS zone; disable storage public network access

Do not rely on outbound IP allowlists for same-region Storage access

App Service outbound IPs are shared across the scale unit and can change. For same-region App Service to Storage traffic, IP-based storage firewall rules are not honored — Microsoft Learn documents that IP network rules have no effect on requests from the same region because same-region Azure services use private Azure IP addresses. Use a service endpoint or private endpoint, not an IP allowlist. See App Service to Azure Storage connectivity for the full decision model.

Network access and data authorization are independent layers

Passing the storage firewall (network layer) does not grant data access. The identity must still hold a data-plane RBAC role such as Storage Blob Data Reader, or use a valid shared key / SAS. A 403 / AuthorizationFailure is ambiguous — it can be a network-rule denial or a missing data-plane role. Confirm the network path is open (and check the Storage error detail) before concluding it is a role gap.

Common Mistakes / Anti-Patterns

  • Enabling VNet Integration but forgetting DNS design.
  • Creating private endpoints without private DNS zone links.
  • Assuming private endpoint also controls outbound traffic.
  • Treating intermittent timeouts as dependency faults without SNAT analysis.
  • Using Hybrid Connections as a permanent architecture without review.

Validation Checklist

Before production cutover:

  • [ ] App can resolve private dependency FQDNs correctly.
  • [ ] App can connect to dependency private endpoints.
  • [ ] Connection pooling is enabled and tuned.
  • [ ] Dependency timeouts are explicit and bounded.
  • [ ] Retry policy prevents connection storms.
  • [ ] NAT/SNAT behavior is observed under peak load tests.

Advanced Topics

  • Use Azure Front Door or Application Gateway with WAF for edge security plus private origin reachability.
  • Add synthetic connectivity probes from inside trusted networks.
  • Build dependency maps with expected DNS resolution source and fallback behavior.
  • Track connection reuse metrics and tune pooling values with load tests.

See Also

Sources