Skip to main content

Challenge 35: Private endpoints for multiple services

Estimated time and cost

60-75 minutes | ~$0.05/h | Exam weight: 10-15%

Scenario

Contoso Enterprise is standardizing private access for all PaaS services consumed by their line-of-business applications. The security team requires that every Azure service used by production workloads must be accessible exclusively over private endpoints, with public access disabled. You must configure private endpoints for six different services, each with its own correct sub-resource (group-id) and privatelink DNS zone name.

Architecture:

Azure VNet: vnet-enterprise (10.0.0.0/16)snet-app (10.0.1.0/24)App VMs / AKSsnet-pe (10.0.2.0/24)PE-Blob.2.4PE-File.2.5PE-SQL.2.6PE-KV.2.7PE-Web.2.8PE-Cosmos.2.9Private DNS Zones● privatelink.blob.core.windows.net● privatelink.file.core.windows.net● privatelink.database.windows.net● privatelink.vaultcore.azure.net● privatelink.azurewebsites.net● privatelink.documents.azure.com

Learning objectives

After completing this challenge you will be able to:

  • Map Azure services to their correct sub-resource group-ids
  • Identify the correct privatelink DNS zone name for each service type
  • Create private endpoints for Storage (blob/file), SQL Database, Key Vault, Web App, and Cosmos DB
  • Verify DNS resolution for each private endpoint
  • Disable public access on services after private endpoints are confirmed working

Prerequisites

  • An Azure subscription with Contributor access
  • Azure CLI installed and authenticated (az login)
  • PowerShell with Az module installed (Install-Module Az -Force)
  • Completion of Challenge 34 (familiarity with PE and DNS concepts)

Key concepts for AZ-700

Service-to-group-id and DNS zone mapping

This is the critical reference table for the exam. Each service has a specific group-id and requires a specific privatelink DNS zone:

ServiceResource typegroup-idPrivate DNS zone name
Storage (Blob)Microsoft.Storage/storageAccountsblobprivatelink.blob.core.windows.net
Storage (File)Microsoft.Storage/storageAccountsfileprivatelink.file.core.windows.net
Storage (Table)Microsoft.Storage/storageAccountstableprivatelink.table.core.windows.net
Storage (Queue)Microsoft.Storage/storageAccountsqueueprivatelink.queue.core.windows.net
Azure SQL DatabaseMicrosoft.Sql/serverssqlServerprivatelink.database.windows.net
Key VaultMicrosoft.KeyVault/vaultsvaultprivatelink.vaultcore.azure.net
Web AppMicrosoft.Web/sitessitesprivatelink.azurewebsites.net
Cosmos DB (SQL API)Microsoft.DocumentDB/databaseAccountsSqlprivatelink.documents.azure.com
Common exam traps
  • SQL Database uses group-id sqlServer (not sql or database)
  • SQL Database DNS zone is privatelink.database.windows.net (not privatelink.sql.database.windows.net)
  • Key Vault DNS zone is privatelink.vaultcore.azure.net (not privatelink.keyvault.azure.net)
  • Cosmos DB SQL API group-id is Sql (capital S) (not sql or cosmosdb)
  • Web App group-id is sites (not webapp or app)
  • Storage requires separate PEs for each sub-resource (blob, file, table, queue)

Task 1: Create the infrastructure

Azure CLI

# Create resource group
az group create \
--name rg-multiservice-pe \
--location eastus2

# Create VNet
az network vnet create \
--resource-group rg-multiservice-pe \
--name vnet-enterprise \
--location eastus2 \
--address-prefixes 10.0.0.0/16 \
--subnet-name snet-app \
--subnet-prefixes 10.0.1.0/24

# Create PE subnet
az network vnet subnet create \
--resource-group rg-multiservice-pe \
--vnet-name vnet-enterprise \
--name snet-pe \
--address-prefixes 10.0.2.0/24

Azure PowerShell

New-AzResourceGroup -Name "rg-multiservice-pe" -Location "eastus2"

$snetApp = New-AzVirtualNetworkSubnetConfig `
-Name "snet-app" -AddressPrefix "10.0.1.0/24"
$snetPe = New-AzVirtualNetworkSubnetConfig `
-Name "snet-pe" -AddressPrefix "10.0.2.0/24"

New-AzVirtualNetwork `
-ResourceGroupName "rg-multiservice-pe" `
-Name "vnet-enterprise" `
-Location "eastus2" `
-AddressPrefix "10.0.0.0/16" `
-Subnet $snetApp, $snetPe

Task 2: Create the target services

Azure CLI

# Storage Account
az storage account create \
--resource-group rg-multiservice-pe \
--name stcontosope01 \
--location eastus2 \
--sku Standard_LRS \
--kind StorageV2

# Azure SQL Server and Database
az sql server create \
--resource-group rg-multiservice-pe \
--name sql-contoso-pe01 \
--location eastus2 \
--admin-user sqladmin \
--admin-password "P@ssw0rd1234!"

az sql db create \
--resource-group rg-multiservice-pe \
--server sql-contoso-pe01 \
--name db-app01 \
--service-objective S0

# Key Vault
az keyvault create \
--resource-group rg-multiservice-pe \
--name kv-contoso-pe01 \
--location eastus2 \
--sku standard

# Web App (requires App Service Plan with PremiumV2 or higher)
az appservice plan create \
--resource-group rg-multiservice-pe \
--name asp-contoso-pe01 \
--location eastus2 \
--sku P1V2

az webapp create \
--resource-group rg-multiservice-pe \
--plan asp-contoso-pe01 \
--name webapp-contoso-pe01 \
--runtime "DOTNET|8.0"

# Cosmos DB (SQL API)
az cosmosdb create \
--resource-group rg-multiservice-pe \
--name cosmos-contoso-pe01 \
--locations regionName=eastus2 failoverPriority=0 \
--kind GlobalDocumentDB

Azure PowerShell

# Storage Account
New-AzStorageAccount `
-ResourceGroupName "rg-multiservice-pe" `
-Name "stcontosope01" `
-Location "eastus2" `
-SkuName "Standard_LRS" `
-Kind "StorageV2"

# SQL Server
New-AzSqlServer `
-ResourceGroupName "rg-multiservice-pe" `
-ServerName "sql-contoso-pe01" `
-Location "eastus2" `
-SqlAdministratorCredentials (New-Object PSCredential("sqladmin", `
(ConvertTo-SecureString "P@ssw0rd1234!" -AsPlainText -Force)))

# SQL Database
New-AzSqlDatabase `
-ResourceGroupName "rg-multiservice-pe" `
-ServerName "sql-contoso-pe01" `
-DatabaseName "db-app01" `
-RequestedServiceObjectiveName "S0"

# Key Vault
New-AzKeyVault `
-ResourceGroupName "rg-multiservice-pe" `
-VaultName "kv-contoso-pe01" `
-Location "eastus2" `
-Sku "Standard"

# Cosmos DB
New-AzCosmosDBAccount `
-ResourceGroupName "rg-multiservice-pe" `
-Name "cosmos-contoso-pe01" `
-Location "eastus2" `
-Kind "GlobalDocumentDB"

Task 3: Create private endpoints for each service

Azure CLI

# --- Storage Blob PE ---
STORAGE_ID=$(az storage account show \
--resource-group rg-multiservice-pe \
--name stcontosope01 --query id -o tsv)

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-storage-blob \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $STORAGE_ID \
--group-id blob \
--connection-name pec-storage-blob

# --- Storage File PE ---
az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-storage-file \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $STORAGE_ID \
--group-id file \
--connection-name pec-storage-file

# --- SQL Database PE ---
SQL_ID=$(az sql server show \
--resource-group rg-multiservice-pe \
--name sql-contoso-pe01 --query id -o tsv)

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-sql \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $SQL_ID \
--group-id sqlServer \
--connection-name pec-sql

# --- Key Vault PE ---
KV_ID=$(az keyvault show \
--resource-group rg-multiservice-pe \
--name kv-contoso-pe01 --query id -o tsv)

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-keyvault \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $KV_ID \
--group-id vault \
--connection-name pec-keyvault

# --- Web App PE ---
WEBAPP_ID=$(az webapp show \
--resource-group rg-multiservice-pe \
--name webapp-contoso-pe01 --query id -o tsv)

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-webapp \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $WEBAPP_ID \
--group-id sites \
--connection-name pec-webapp

# --- Cosmos DB PE (SQL API) ---
COSMOS_ID=$(az cosmosdb show \
--resource-group rg-multiservice-pe \
--name cosmos-contoso-pe01 --query id -o tsv)

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-cosmosdb \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $COSMOS_ID \
--group-id Sql \
--connection-name pec-cosmosdb

Azure PowerShell (example for SQL and Key Vault)

$vnet = Get-AzVirtualNetwork -ResourceGroupName "rg-multiservice-pe" -Name "vnet-enterprise"
$subnet = $vnet.Subnets | Where-Object { $_.Name -eq "snet-pe" }

# SQL Database PE
$sqlServer = Get-AzSqlServer -ResourceGroupName "rg-multiservice-pe" -ServerName "sql-contoso-pe01"
$sqlConnection = New-AzPrivateLinkServiceConnection `
-Name "pec-sql" `
-PrivateLinkServiceId $sqlServer.ResourceId `
-GroupId "sqlServer"

New-AzPrivateEndpoint `
-ResourceGroupName "rg-multiservice-pe" `
-Name "pe-sql" `
-Location "eastus2" `
-Subnet $subnet `
-PrivateLinkServiceConnection $sqlConnection

# Key Vault PE
$kv = Get-AzKeyVault -ResourceGroupName "rg-multiservice-pe" -VaultName "kv-contoso-pe01"
$kvConnection = New-AzPrivateLinkServiceConnection `
-Name "pec-keyvault" `
-PrivateLinkServiceId $kv.ResourceId `
-GroupId "vault"

New-AzPrivateEndpoint `
-ResourceGroupName "rg-multiservice-pe" `
-Name "pe-keyvault" `
-Location "eastus2" `
-Subnet $subnet `
-PrivateLinkServiceConnection $kvConnection
![Challenge 35 - Network Topology](/img/az-700/challenge-35-topology.svg)


---

## Task 5: Verify DNS resolution for each service

```bash
# From a VM inside vnet-enterprise, verify each service resolves to a private IP

# Storage Blob
nslookup stcontosope01.blob.core.windows.net
# Expected: stcontosope01.privatelink.blob.core.windows.net → 10.0.2.x

# Storage File
nslookup stcontosope01.file.core.windows.net
# Expected: stcontosope01.privatelink.file.core.windows.net → 10.0.2.x

# SQL Database
nslookup sql-contoso-pe01.database.windows.net
# Expected: sql-contoso-pe01.privatelink.database.windows.net → 10.0.2.x

# Key Vault
nslookup kv-contoso-pe01.vault.azure.net
# Expected: kv-contoso-pe01.privatelink.vaultcore.azure.net → 10.0.2.x

# Web App
nslookup webapp-contoso-pe01.azurewebsites.net
# Expected: webapp-contoso-pe01.privatelink.azurewebsites.net → 10.0.2.x

# Cosmos DB
nslookup cosmos-contoso-pe01.documents.azure.com
# Expected: cosmos-contoso-pe01.privatelink.documents.azure.com → 10.0.2.x

Task 6: Disable public access after PE verification

Only disable public access after confirming private endpoints are working correctly.

Azure CLI

# Disable public access on Storage
az storage account update \
--resource-group rg-multiservice-pe \
--name stcontosope01 \
--public-network-access Disabled

# Disable public access on SQL Server
az sql server update \
--resource-group rg-multiservice-pe \
--name sql-contoso-pe01 \
--public-network-access Disabled

# Disable public access on Key Vault
az keyvault update \
--resource-group rg-multiservice-pe \
--name kv-contoso-pe01 \
--public-network-access Disabled

# Disable public access on Cosmos DB
az cosmosdb update \
--resource-group rg-multiservice-pe \
--name cosmos-contoso-pe01 \
--public-network-access Disabled

# Web App - restrict access to VNet only
az webapp update \
--resource-group rg-multiservice-pe \
--name webapp-contoso-pe01 \
--set publicNetworkAccess=Disabled
Order of operations

Always verify that private endpoint DNS resolution is working before disabling public access. If you disable public access first and the PE/DNS is misconfigured, you will lose all connectivity to the service (including management plane in some cases). This is one of the most common production incidents with private endpoints.


Break & fix

Scenario 1: Wrong group-id used

Symptom: Private endpoint for Cosmos DB shows as connected, but the application cannot reach the SQL API endpoint.

Diagnosis:

az network private-endpoint show \
--resource-group rg-multiservice-pe \
--name pe-cosmosdb \
--query "privateLinkServiceConnections[0].groupIds[0]" \
--output tsv

Root cause: The group-id was set to sql (lowercase) instead of Sql (capital S), or a different group-id like MongoDB was used for a SQL API account.

Fix: Delete and recreate the PE with the correct group-id:

az network private-endpoint delete \
--resource-group rg-multiservice-pe \
--name pe-cosmosdb

az network private-endpoint create \
--resource-group rg-multiservice-pe \
--name pe-cosmosdb \
--vnet-name vnet-enterprise \
--subnet snet-pe \
--private-connection-resource-id $COSMOS_ID \
--group-id Sql \
--connection-name pec-cosmosdb

Scenario 2: Incorrect DNS zone name for SQL

Symptom: nslookup sql-contoso-pe01.database.windows.net returns the public IP even though the PE exists and DNS zone is created.

Diagnosis:

# List DNS zones and check for typo
az network private-dns zone list \
--resource-group rg-multiservice-pe \
--query "[].name" \
--output tsv

Root cause: The DNS zone was created as privatelink.sql.database.windows.net (incorrect) instead of privatelink.database.windows.net (correct).

Fix:

# Delete the incorrect zone
az network private-dns zone delete \
--resource-group rg-multiservice-pe \
--name "privatelink.sql.database.windows.net" \
--yes

# Create the correct zone
az network private-dns zone create \
--resource-group rg-multiservice-pe \
--name "privatelink.database.windows.net"

az network private-dns link vnet create \
--resource-group rg-multiservice-pe \
--zone-name "privatelink.database.windows.net" \
--name link-vnet-enterprise \
--virtual-network vnet-enterprise \
--registration-enabled false

# Recreate the DNS zone group
az network private-endpoint dns-zone-group create \
--resource-group rg-multiservice-pe \
--endpoint-name pe-sql \
--name zg-sql \
--private-dns-zone "privatelink.database.windows.net" \
--zone-name sqlServer

Scenario 3: Disabling public access before PE is ready

Symptom: After disabling public access on the storage account, all applications (including those on the VNet) lose connectivity.

Diagnosis:

# Verify PE connection status
az network private-endpoint show \
--resource-group rg-multiservice-pe \
--name pe-storage-blob \
--query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status" \
--output tsv

# Check if DNS zone group was created
az network private-endpoint dns-zone-group list \
--resource-group rg-multiservice-pe \
--endpoint-name pe-storage-blob \
--output table

Root cause: Public access was disabled before the DNS zone group was configured, so DNS still resolves to a public IP (which is now blocked).

Fix: Re-enable public access temporarily, fix the DNS, then disable again:

# Re-enable public access
az storage account update \
--resource-group rg-multiservice-pe \
--name stcontosope01 \
--public-network-access Enabled

# Create missing DNS zone group
az network private-endpoint dns-zone-group create \
--resource-group rg-multiservice-pe \
--endpoint-name pe-storage-blob \
--name zg-blob \
--private-dns-zone "privatelink.blob.core.windows.net" \
--zone-name blob

# Verify resolution from VNet VM, then disable public access
az storage account update \
--resource-group rg-multiservice-pe \
--name stcontosope01 \
--public-network-access Disabled

Knowledge check

1. What is the correct group-id value when creating a private endpoint for Azure SQL Database?

2. Which private DNS zone name is correct for Azure Key Vault private endpoints?

3. A storage account needs private access for both blob and file shares. How many private endpoints are required?

4. What is the correct private DNS zone for Azure Cosmos DB with SQL API?

5. You disabled public network access on a SQL server but applications in the VNet cannot connect. DNS resolution shows the public IP. What should you check first?

6. What is the group-id for creating a private endpoint to an Azure Web App?


Cleanup

Remove all resources created in this challenge to stop billing:

az group delete --name rg-multiservice-pe --yes --no-wait
Remove-AzResourceGroup -Name "rg-multiservice-pe" -Force -AsJob
Cost warning

This challenge creates multiple billable resources: private endpoints (~$0.01/h each), an App Service Plan (P1V2 ~$0.035/h), a SQL Database (S0 $0.02/h), a Cosmos DB account ($0.008/h minimum), and a Key Vault. Total estimated cost is approximately $0.05/h. Delete the resource group promptly after completing the lab.


Additional references