Skip to content

VNet Integration

Enable VNet integration for an ASP.NET Core 8 app so outbound calls to SQL, Redis, and Key Vault stay on private network paths.

flowchart TD
    A[ASP.NET Core app on App Service] --> B[VNet integration subnet]
    B --> C[Private Endpoint SQL]
    B --> D[Private Endpoint Key Vault]
    B --> E[Private Endpoint Redis]
    B --> F[NSG and UDR controls]

Prerequisites

  • App Service Plan tier that supports VNet integration
  • Existing virtual network and delegated subnet for App Service
  • Permissions to configure VNet, NSG, private endpoints, and DNS
  • ASP.NET Core 8 app running on Windows App Service (do not use --is-linux for .NET setup)

Main Content

1) Create delegated subnet for integration

az network vnet subnet create \
  --resource-group "$RG" \
  --vnet-name "vnet-appservice" \
  --name "snet-appservice-integration" \
  --address-prefixes "10.10.1.0/24" \
  --delegations "Microsoft.Web/serverFarms" \
  --output json
| Flag | Description | |---|---| | az network vnet subnet create | Creates the subnet that App Service will use for VNet integration. | | --resource-group "$RG" | Targets the resource group that contains the virtual network. | | --vnet-name "vnet-appservice" | Selects the virtual network where the subnet will be created. | | --name "snet-appservice-integration" | Names the delegated integration subnet. | | --address-prefixes "10.10.1.0/24" | Allocates the subnet address range for integration instances. | | --delegations "Microsoft.Web/serverFarms" | Delegates the subnet to App Service plans for VNet integration. | | --output json | Returns the created subnet resource as JSON. |

2) Attach App Service to subnet

az webapp vnet-integration add \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --vnet "vnet-appservice" \
  --subnet "snet-appservice-integration" \
  --output json
| Flag | Description | |---|---| | az webapp vnet-integration add | Connects the web app to the delegated subnet for outbound private networking. | | --resource-group "$RG" | Targets the resource group that contains the web app. | | --name "$APP_NAME" | Selects the web app that will join the subnet. | | --vnet "vnet-appservice" | Uses the specified virtual network for the integration. | | --subnet "snet-appservice-integration" | Attaches the app to the delegated integration subnet. | | --output json | Returns the VNet-integration configuration as JSON. |

3) Enable route-all for controlled outbound (optional)

az webapp config appsettings set \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --settings WEBSITE_VNET_ROUTE_ALL=1 \
  --output json
| Flag | Description | |---|---| | az webapp config appsettings set | Creates or updates app settings on the web app. | | --resource-group "$RG" | Targets the resource group that contains the web app. | | --name "$APP_NAME" | Selects the web app whose routing behavior will change. | | --settings WEBSITE_VNET_ROUTE_ALL=1 | Forces outbound traffic from the app through the integrated VNet path. | | --output json | Returns the updated app-settings payload as JSON. |

4) Apply NSG policy baseline

Allow only required outbound traffic from integration subnet (for example ports 1433, 6380, 443), and monitor NSG flow logs.

Deploy private endpoints for SQL, Key Vault, and Redis in dedicated subnet(s), then link these zones to the app VNet:

  • privatelink.database.windows.net
  • privatelink.vaultcore.azure.net
  • privatelink.redis.cache.windows.net

6) Configure dependency settings in appsettings.json

{
  "ConnectionStrings": {
    "SqlServer": "Server=tcp:<sql-private-fqdn>,1433;Database=<db-name>;Encrypt=True;TrustServerCertificate=False;"
  },
  "Redis": {
    "Host": "<redis-private-fqdn>",
    "Port": 6380,
    "Ssl": true
  },
  "KeyVault": {
    "Uri": "https://<kv-name>.vault.azure.net/"
  }
}

7) Use DefaultAzureCredential, Microsoft.Data.SqlClient, and StackExchange.Redis

using Azure.Identity;
using Microsoft.Data.SqlClient;
using StackExchange.Redis;

var credential = new DefaultAzureCredential();
var sqlToken = (await credential.GetTokenAsync(
    new Azure.Core.TokenRequestContext(new[] { "https://database.windows.net/.default" })
)).Token;

var connectionString = builder.Configuration.GetConnectionString("SqlServer");
using var sqlConnection = new SqlConnection(connectionString)
{
    AccessToken = sqlToken
};
await sqlConnection.OpenAsync();

var redisHost = builder.Configuration["Redis:Host"];
var redisPort = builder.Configuration["Redis:Port"];
var redis = await ConnectionMultiplexer.ConnectAsync($"{redisHost}:{redisPort},ssl=True,abortConnect=False");

8) Add CI validation checks for integration and endpoints

- name: Validate VNet integration and private endpoints
  run: |
    az webapp vnet-integration list \
      --resource-group "$RG" \
      --name "$APP_NAME" \
      --output table
    az network private-endpoint list \
      --resource-group "$RG" \
      --output table

Inbound versus outbound

VNet integration governs outbound connectivity from App Service. Inbound private access requires separate App Service private endpoint configuration.

Verification

  • az webapp vnet-integration list reports expected subnet integration.
  • SQL, Redis, and Key Vault names resolve to private IPs in app diagnostics.
  • ASP.NET Core app can connect without public network dependency.

Troubleshooting

SQL authentication or connection failure

  • Confirm system-assigned managed identity is enabled.
  • Verify Microsoft Entra SQL permissions for the app identity.
  • Validate NSG/UDR path to SQL private endpoint.

Redis connection timeout

  • Confirm private DNS resolution for Redis hostname.
  • Validate outbound allowance on TCP 6380.

Connectivity regression after route-all

  • Inspect route table and next hop configuration.
  • Confirm required Azure control plane dependencies remain reachable.

Run It in the Portal

Portal view: Networking blade (Virtual network integration not yet configured)

Networking blade for the `app-appsvc-guide-recap` Web App split into an Inbound traffic configuration card and an Outbound traffic configuration card, with the command bar Refresh, Troubleshoot, and Send us your feedback. The Inbound card shows Public network access "Enabled with no access restrictions (Using default behavior)", App assigned address "Not configured", Private endpoints "0 private endpoints", Inbound IPv4 addresses `192.0.2.1`, and Inbound IPv6 addresses `2001:db8::1`; an Optional inbound services subsection lists Azure Front Door with a "View details" link. The Outbound card shows Virtual network integration "Not configured", Hybrid connections "Not configured", Outbound DNS "Default (Azure-provided)", and long comma-separated Outbound IPv4 (`192.0.2.1`) and Outbound IPv6 (`2001:db8::1`) address lists; an Integration subnet configuration subsection shows NAT gateway, Network security group, and User defined route all "N/A". The left navigation shows Networking highlighted under Settings.

The Networking blade is the Portal surface where this recipe's outbound VNet connection becomes visible for the ASP.NET Core app. In the pre-recipe state shown here, Virtual network integration: Not configured is the key row; after az webapp vnet-integration add, this is the row to revisit to confirm the app is attached to the delegated subnet, which is the precondition for the recipe's Microsoft.Data.SqlClient and StackExchange.Redis connections to reach backend services over the private network. The Inbound traffic configuration card remains separate, which matches the recipe's point that VNet integration changes outbound connectivity rather than inbound exposure on its own.

See Also

Sources