Skip to content

Private Endpoints

Connect an Express app on App Service to SQL, Redis, and Key Vault over private endpoints while preserving standard Azure service DNS names.

flowchart TD
    A[Express app on App Service with VNet integration] --> B[Private DNS zones]
    B --> C[Private Endpoint SQL]
    B --> D[Private Endpoint Redis]
    B --> E[Private Endpoint Key Vault]
    C --> F[Private backend connectivity]
    D --> F
    E --> F

Prerequisites

  • App Service Plan tier that supports VNet integration
  • Virtual network with integration and private endpoint subnets
  • Azure SQL, Redis, and Key Vault resources ready for private endpoint

Main Content

1) Prepare subnet and DNS architecture

Recommended layout:

  • snet-appservice-integration for VNet integration
  • snet-private-endpoints for private endpoint NICs
  • Private DNS zones linked to the VNet

2) Enable VNet integration for the app

az webapp vnet-integration add \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --vnet "$VNET_NAME" \
  --subnet "snet-appservice-integration" \
  --output json
Flag Description
--resource-group "$RG" Targets the resource group that contains the web app and VNet.
--name "$APP_NAME" Selects the web app that should use VNet integration.
--vnet "$VNET_NAME" Chooses the virtual network that provides the outbound private path.
--subnet "snet-appservice-integration" Attaches the app to the delegated subnet reserved for App Service integration.
--output json Returns the VNet integration result as JSON.

3) Create private endpoint for Azure SQL

az network private-endpoint create \
  --resource-group "$RG" \
  --name "pe-sql-node" \
  --vnet-name "$VNET_NAME" \
  --subnet "snet-private-endpoints" \
  --private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<sql-rg>/providers/Microsoft.Sql/servers/<sql-server>" \
  --group-id sqlServer \
  --connection-name "pe-sql-node-conn" \
  --output json
Flag Description
--resource-group "$RG" Creates the private endpoint resource in the specified resource group.
--name "pe-sql-node" Names the private endpoint resource and its NIC-related artifacts.
--vnet-name "$VNET_NAME" Places the private endpoint inside the specified virtual network.
--subnet "snet-private-endpoints" Uses the subnet reserved for private endpoint NICs.
--private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<sql-rg>/providers/Microsoft.Sql/servers/<sql-server>" Points the private endpoint at the Azure SQL server resource that should be reached privately.
--group-id sqlServer Selects the Azure SQL private-link subresource for the connection.
--connection-name "pe-sql-node-conn" Names the private-link connection object created for the SQL endpoint.
--output json Returns the created private endpoint details as JSON.

4) Create private endpoints for Redis and Key Vault

az network private-endpoint create \
  --resource-group "$RG" \
  --name "pe-redis-node" \
  --vnet-name "$VNET_NAME" \
  --subnet "snet-private-endpoints" \
  --private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<redis-rg>/providers/Microsoft.Cache/Redis/<redis-name>" \
  --group-id redisCache \
  --connection-name "pe-redis-node-conn" \
  --output json

az network private-endpoint create \
  --resource-group "$RG" \
  --name "pe-kv-node" \
  --vnet-name "$VNET_NAME" \
  --subnet "snet-private-endpoints" \
  --private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<kv-rg>/providers/Microsoft.KeyVault/vaults/<kv-name>" \
  --group-id vault \
  --connection-name "pe-kv-node-conn" \
  --output json
Command Description
az network private-endpoint create --resource-group "$RG" --name "pe-redis-node" --vnet-name "$VNET_NAME" --subnet "snet-private-endpoints" --private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<redis-rg>/providers/Microsoft.Cache/Redis/<redis-name>" --group-id redisCache --connection-name "pe-redis-node-conn" --output json Creates a private endpoint and private-link connection for the Azure Cache for Redis instance.
az network private-endpoint create --resource-group "$RG" --name "pe-kv-node" --vnet-name "$VNET_NAME" --subnet "snet-private-endpoints" --private-connection-resource-id "/subscriptions/<subscription-id>/resourceGroups/<kv-rg>/providers/Microsoft.KeyVault/vaults/<kv-name>" --group-id vault --connection-name "pe-kv-node-conn" --output json Creates a private endpoint and private-link connection for the Key Vault.

5) Configure environment variables for Express app

az webapp config appsettings set \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --settings \
    SQL_SERVER_FQDN="<sql-server>.database.windows.net" \
    SQL_DATABASE_NAME="<db-name>" \
    REDIS_HOST="<redis-name>.redis.cache.windows.net" \
    REDIS_PORT="6380" \
    KEY_VAULT_URI="https://<kv-name>.vault.azure.net/" \
  --output json
Flag Description
--resource-group "$RG" Targets the resource group that contains the web app.
--name "$APP_NAME" Selects the web app that should receive the backend connection settings.
--settings SQL_SERVER_FQDN="<sql-server>.database.windows.net" SQL_DATABASE_NAME="<db-name>" REDIS_HOST="<redis-name>.redis.cache.windows.net" REDIS_PORT="6380" KEY_VAULT_URI="https://<kv-name>.vault.azure.net/" Writes the SQL, Redis, and Key Vault hostnames and related settings that the app will use over private networking.
--output json Returns the updated app-settings payload as JSON.

6) Use process.env, @azure/identity, mssql, and ioredis

const sql = require("mssql");
const Redis = require("ioredis");
const { DefaultAzureCredential } = require("@azure/identity");

const credential = new DefaultAzureCredential();

async function openSqlConnection() {
  const tokenResponse = await credential.getToken("https://database.windows.net/.default");
  return sql.connect({
    server: process.env.SQL_SERVER_FQDN,
    database: process.env.SQL_DATABASE_NAME,
    options: {
      encrypt: true,
    },
    authentication: {
      type: "azure-active-directory-access-token",
      options: {
        token: tokenResponse.token,
      },
    },
  });
}

const redisClient = new Redis({
  host: process.env.REDIS_HOST,
  port: Number(process.env.REDIS_PORT),
  tls: {},
});

Ensure links exist for:

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

8) Add CI networking validation step

- name: Validate private endpoint state
  run: |
    az network private-endpoint list \
      --resource-group "$RG" \
      --output table

Private endpoint setup requires DNS correctness

Most incidents are caused by missing or incorrect private DNS links. Validate name resolution before debugging Express application code.

Verification

  1. Confirm app has VNet integration.
  2. Confirm each private endpoint is in Approved state.
  3. Confirm SQL, Redis, and Key Vault hostnames resolve to private IP addresses.
az webapp vnet-integration list \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --output table
Flag Description
--resource-group "$RG" Looks up VNet integration on the web app in the specified resource group.
--name "$APP_NAME" Selects the web app whose integration mapping should be listed.
--output table Formats the integration list as a readable table for verification.

Troubleshooting

SQL login timeout or socket errors

  • Verify NSG allows outbound 1433 from integration subnet.
  • Confirm SQL private endpoint approval and firewall settings.
  • Check token acquisition with managed identity.

Redis connection reset or timeout

  • Confirm private DNS resolves Redis hostname to private IP.
  • Verify Redis TLS port 6380 is reachable.

Key Vault calls fail intermittently

  • Validate route table and DNS path for vault.azure.net via private link.
  • Check App Service managed identity permissions on Key Vault.

Run It in the Portal

Portal view: Networking blade (app-side precondition for backend private endpoints)

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: This capture shows the displayed configuration of a public app without VNet integration: the listed outbound addresses are the platform's potential outbound address pool (not a measured egress address for any connection), and the integration subnet settings show N/A.

This web-app Networking blade is a supporting before-state for the recipe rather than the place where the SQL, Redis, and Key Vault private endpoints themselves are listed. The visible Virtual network integration: Not configured row is the app-side prerequisite the recipe changes before private DNS for those backend services can work, while Private endpoints: 0 private endpoints also makes clear the screenshot is not showing downstream private endpoints attached to other resources. Use this capture as the pre-integration checkpoint before running the recipe's VNet and backend private-endpoint steps for the Node.js app.

See Also

Sources