Azure App Service VNet Integration DNS Private Endpoint

App Service with VNet Integration fails to resolve private DNS names

Fix DNS resolution in App Service with VNet Integration: configure Private DNS Zones, VNet links and WEBSITE_DNS_SERVER.

·
App Service with VNet Integration enabled connects to Azure SQL Database, a Storage Account or another resource via its public IP instead of the private one. Although a Private Endpoint is configured, the application resolves the hostname to a public address or receives NXDOMAIN. Traffic bypasses Private Link and traverses the public internet, violating security policies and potentially being blocked by the target resource's firewall.

This runbook covers DNS resolution issues in App Service with VNet Integration and Private Endpoints. For a full guide on migrating .NET infrastructure to Azure, see Migrating .NET Workloads to Azure - App Service, AKS, Landing Zone. For network architecture and Private Link guidance, book a consulting session.

Symptoms

The connection from App Service to a resource with a Private Endpoint times out or fails. The Kudu console (Advanced Tools) shows a public IP instead of a private one:

# In Kudu console (https://<app-name>.scm.azurewebsites.net)
# Tools > Debug console > CMD or PowerShell

# nslookup returns public IP instead of private (10.x.x.x)
nameresolver mysqlserver.database.windows.net
# Problematic result: returns e.g. 52.168.112.64 (public)
# Correct result: should return e.g. 10.0.1.5 (private from Private Endpoint)

# Alternatively via PowerShell in Kudu
[System.Net.Dns]::GetHostAddresses("mysqlserver.database.windows.net") | Select IPAddressToString
# Problematic result: 52.168.112.64
# Correct result: 10.0.1.5

# Check whether WEBSITE_DNS_SERVER is set
env | grep DNS
# If no WEBSITE_DNS_SERVER entry - that is part of the problem

# TCP connectivity test to Private Endpoint
tcpping mysqlserver.database.windows.net:1433
# Problematic result: Connection timed out or connects to public IP

The application logs errors such as “Connection timed out” or “Login failed” (if the resource firewall only allows Private Endpoint access). Application Insights shows dependency failures to Azure SQL or Storage.

Cause

App Service with VNet Integration requires correct DNS configuration to resolve Private Endpoint names to private addresses. The problem occurs when:

  • Private DNS Zone is not linked to the VNet: The zone privatelink.database.windows.net (or the appropriate zone for the resource) exists but has no VNet Link to the VNet containing the App Service integration subnet. Without the link, DNS queries from the VNet do not reach the Private DNS Zone.

  • WEBSITE_DNS_SERVER is not set to 168.63.129.16: By default, App Service uses Azure’s public DNS servers. After enabling VNet Integration you must explicitly set WEBSITE_DNS_SERVER=168.63.129.16 (Azure DNS resolver) so that DNS queries traverse the VNet and reach Private DNS Zones.

  • Route All (vnetRouteAllEnabled) is not enabled: Without this option, only traffic to RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) is routed through the VNet. DNS queries to 168.63.129.16 and other traffic may still exit outside the VNet.

  • DNS Zone name does not match the resource suffix: The Private DNS Zone must have a name that exactly matches the Private Link suffix for the service. For example, Azure SQL requires the zone privatelink.database.windows.net - not database.windows.net or privatelink.sql.azuresynapse.net. An incorrect zone name means the A record is never found.

Fix

A) Link the Private DNS Zone to the App Service VNet:

# Check whether the Private DNS Zone exists
az network private-dns zone list \
  --query "[].{name:name, records:numberOfRecordSets, links:numberOfVirtualNetworkLinks}" -o table

# Check existing VNet links for the zone
az network private-dns link vnet list \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --query "[].{name:name, vnetId:virtualNetwork.id, state:provisioningState}" -o table

# Add a link to the VNet containing the App Service subnet
az network private-dns link vnet create \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --name link-vnet-appservice \
  --virtual-network /subscriptions/<sub-id>/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-production \
  --registration-enabled false

# Verify that the A record exists in the zone
az network private-dns record-set a list \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --query "[].{name:name, ip:aRecords[0].ipv4Address}" -o table
# Expected: mysqlserver -> 10.0.1.5

B) Set WEBSITE_DNS_SERVER to 168.63.129.16:

# Check current app settings
az webapp config appsettings list \
  --resource-group rg-app \
  --name my-app-service \
  --query "[?name=='WEBSITE_DNS_SERVER'].value" -o tsv

# Set Azure DNS resolver as the DNS server for App Service
az webapp config appsettings set \
  --resource-group rg-app \
  --name my-app-service \
  --settings WEBSITE_DNS_SERVER=168.63.129.16

# Optionally - set WEBSITE_DNS_ALT_SERVER as a fallback
az webapp config appsettings set \
  --resource-group rg-app \
  --name my-app-service \
  --settings WEBSITE_DNS_ALT_SERVER=168.63.129.16

C) Enable Route All (vnetRouteAllEnabled):

# Check current VNet Integration and Route All state
az webapp show \
  --resource-group rg-app \
  --name my-app-service \
  --query '{vnetIntegration:virtualNetworkSubnetId, routeAll:vnetRouteAllEnabled}' -o json

# Enable Route All - all traffic from App Service will route through the VNet
az webapp update \
  --resource-group rg-app \
  --name my-app-service \
  --set vnetRouteAllEnabled=true

# Alternatively via az resource update (for older CLI versions)
az resource update \
  --resource-group rg-app \
  --name my-app-service \
  --resource-type Microsoft.Web/sites \
  --set properties.vnetRouteAllEnabled=true
// Equivalent configuration in ARM template / Bicep
{
  "type": "Microsoft.Web/sites",
  "properties": {
    "virtualNetworkSubnetId": "/subscriptions/<sub-id>/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-production/subnets/subnet-appservice",
    "vnetRouteAllEnabled": true,
    "siteConfig": {
      "appSettings": [
        {
          "name": "WEBSITE_DNS_SERVER",
          "value": "168.63.129.16"
        }
      ]
    }
  }
}

D) Verify the DNS record in the Private DNS Zone is correct:

# Check the correct Private DNS Zone name for each Azure service:
# Azure SQL Database:       privatelink.database.windows.net
# Azure Blob Storage:       privatelink.blob.core.windows.net
# Azure Key Vault:          privatelink.vaultcore.azure.net
# Azure Cosmos DB:          privatelink.documents.azure.com
# Azure Service Bus:        privatelink.servicebus.windows.net

# Check whether the Private Endpoint was correctly registered in the zone
az network private-endpoint dns-zone-group list \
  --resource-group rg-data \
  --endpoint-name pe-sqlserver \
  --query "[].{zone:privateDnsZoneConfigs[0].privateDnsZoneId, records:privateDnsZoneConfigs[0].recordSets}" -o json

# If the record does not exist - add it manually (or fix the dns-zone-group)
az network private-dns record-set a add-record \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --record-set-name mysqlserver \
  --ipv4-address 10.0.1.5

# Check that the Private Endpoint has the correct IP in the subnet
az network private-endpoint show \
  --resource-group rg-data \
  --name pe-sqlserver \
  --query '{ip:customDnsConfigs[0].ipAddresses[0], fqdn:customDnsConfigs[0].fqdn, subnet:subnet.id}' -o json

After applying changes, the App Service may need a restart to flush the DNS cache:

# Restart App Service
az webapp restart \
  --resource-group rg-app \
  --name my-app-service

Verification

# 1. In Kudu console - check that DNS resolves to a private IP
# https://<app-name>.scm.azurewebsites.net > Debug console > CMD
nameresolver mysqlserver.database.windows.net
# Expected: Addresses: 10.0.1.5 (private IP from the Private Endpoint subnet)
# 2. In Kudu PowerShell - DNS resolution test
[System.Net.Dns]::GetHostAddresses("mysqlserver.database.windows.net") | Select IPAddressToString
# Expected: 10.0.1.5
# 3. TCP connectivity test to Private Endpoint
tcpping mysqlserver.database.windows.net:1433
# Expected: Connected to mysqlserver.database.windows.net:1433, time=Xms

# 4. Confirm configuration is complete via Azure CLI
az webapp show \
  --resource-group rg-app \
  --name my-app-service \
  --query '{routeAll:vnetRouteAllEnabled, vnet:virtualNetworkSubnetId}' -o json
# Expected: routeAll=true, vnet points to the integration subnet

az webapp config appsettings list \
  --resource-group rg-app \
  --name my-app-service \
  --query "[?name=='WEBSITE_DNS_SERVER'].{name:name, value:value}" -o table
# Expected: WEBSITE_DNS_SERVER = 168.63.129.16

If nameresolver returns a private IP (10.x.x.x) and tcpping connects successfully - the issue is resolved. Verify in Application Insights that dependency calls to Azure SQL/Storage no longer generate errors.

When App Service resolves a Private Endpoint to a public IP, all database or storage traffic traverses the public internet instead of the Azure private backbone. This means data leaves the private network perimeter, potentially breaching compliance requirements (e.g. ISO 27001, GDPR data residency), being blocked by the resource firewall (if set to "Deny public access"), and incurring higher latency and egress costs. In production environments this is often the root cause of unexplained timeouts that teams spend hours diagnosing before checking the DNS layer.

 

Jerzy Kopaczewski

Private Endpoints not working despite VNet Integration?

Book a free 30-minute call. We will review your DNS configuration, Private Link and VNet Integration to ensure traffic flows securely over the Azure private network.