Skip to main content
AzureNetworkingintermediate

Azure Private Endpoint Guide

Secure PaaS services with Private Endpoints: DNS resolution, cross-region, hub-spoke, and troubleshooting.

CloudToolStack Team23 min readPublished Mar 14, 2026

Prerequisites

  • Understanding of Azure VNet concepts (subnets, NSGs, peering)
  • Familiarity with DNS resolution fundamentals

What Are Azure Private Endpoints?

Azure Private Endpoints provide private connectivity from your virtual network (VNet) to Azure PaaS services (Storage, SQL Database, Cosmos DB, Key Vault, etc.) without traversing the public internet. A private endpoint is a network interface with a private IP address in your VNet that connects to a specific Azure service. Traffic between your VNet and the service travels entirely over the Microsoft backbone network.

Without private endpoints, applications in your VNet communicate with Azure PaaS services over their public endpoints. Even though this traffic is encrypted with TLS, it routes over the public internet, which may violate compliance requirements, expose the service to public discovery, and create a larger attack surface. Private endpoints eliminate these concerns by keeping all traffic private and removing the service's public endpoint entirely.

This guide covers private endpoint architecture, DNS resolution (the trickiest part), cross-region and cross-VNet configurations, Terraform automation, troubleshooting common issues, and integration with hub-spoke network topologies.

Service Endpoints vs Private Endpoints

Service Endpoints keep traffic on the Azure backbone but use the service's public IP. They are simpler and free but do not provide a private IP. Private Endpoints assign a private IP in your VNet, enabling access from on-premises via VPN/ExpressRoute and providing full DNS-level isolation. Use Private Endpoints for production workloads requiring compliance; use Service Endpoints for simpler scenarios where cost matters.

Creating a Private Endpoint

Creating a private endpoint involves three components: the private endpoint resource itself, a network interface with a private IP, and a private DNS zone for name resolution. Without proper DNS configuration, applications will still resolve to the service's public IP, bypassing the private endpoint entirely.

bash
# Create a private endpoint for Azure SQL Database
az network private-endpoint create \
  --name "sql-private-endpoint" \
  --resource-group myapp-rg \
  --vnet-name myapp-vnet \
  --subnet private-endpoints-subnet \
  --private-connection-resource-id "/subscriptions/SUB_ID/resourceGroups/myapp-rg/providers/Microsoft.Sql/servers/myapp-sql" \
  --group-id sqlServer \
  --connection-name "sql-pe-connection"

# Get the private IP assigned to the endpoint
az network private-endpoint show \
  --name sql-private-endpoint \
  --resource-group myapp-rg \
  --query 'customDnsConfigs[].{FQDN:fqdn, IP:ipAddresses[0]}' \
  --output table

# Disable public access on the SQL Server
az sql server update \
  --name myapp-sql \
  --resource-group myapp-rg \
  --public-network-access Disabled

DNS Resolution: The Critical Piece

DNS is the most complex and error-prone aspect of private endpoints. When you create a private endpoint, Azure does NOT automatically update DNS. Your application must resolve the service's FQDN (e.g., myapp-sql.database.windows.net) to the private IP address. Without this, the application will connect via the public endpoint, which defeats the purpose of the private endpoint.

Azure provides Private DNS Zones that integrate with your VNet to provide automatic DNS resolution. Each Azure service has a specific private DNS zone name that you must create and link to your VNet.

Private DNS Zone Names by Service

ServicePrivate DNS ZoneGroup ID
Azure SQLprivatelink.database.windows.netsqlServer
Blob Storageprivatelink.blob.core.windows.netblob
Key Vaultprivatelink.vaultcore.azure.netvault
Cosmos DBprivatelink.documents.azure.comSql
Azure Container Registryprivatelink.azurecr.ioregistry
Azure App Serviceprivatelink.azurewebsites.netsites
Event Hubsprivatelink.servicebus.windows.netnamespace
bash
# Create the private DNS zone
az network private-dns zone create \
  --name "privatelink.database.windows.net" \
  --resource-group myapp-rg

# Link the DNS zone to your VNet
az network private-dns zone vnet-link create \
  --name "sql-dns-link" \
  --zone-name "privatelink.database.windows.net" \
  --resource-group myapp-rg \
  --virtual-network myapp-vnet \
  --registration-enabled false

# Create DNS records for the private endpoint
az network private-endpoint dns-zone-group create \
  --endpoint-name sql-private-endpoint \
  --resource-group myapp-rg \
  --name "sql-dns-group" \
  --private-dns-zone "/subscriptions/SUB_ID/resourceGroups/myapp-rg/providers/Microsoft.Network/privateDnsZones/privatelink.database.windows.net" \
  --zone-name "sql"

# Verify DNS resolution from inside the VNet
# nslookup myapp-sql.database.windows.net
# Should return: myapp-sql.privatelink.database.windows.net -> 10.0.3.5 (private IP)

DNS Resolution from On-Premises

If you access Azure services from on-premises via VPN or ExpressRoute, you must configure DNS forwarding. On-premises DNS servers need to forward queries for*.privatelink.database.windows.net to Azure DNS (168.63.129.16) through a DNS forwarder VM in your VNet. Without this, on-premises clients will resolve public IPs and bypass the private endpoint.

Terraform Automation

hcl
# Private endpoint for Azure SQL Database
resource "azurerm_private_endpoint" "sql" {
  name                = "sql-private-endpoint"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  subnet_id           = azurerm_subnet.private_endpoints.id

  private_service_connection {
    name                           = "sql-pe-connection"
    private_connection_resource_id = azurerm_mssql_server.main.id
    subresource_names              = ["sqlServer"]
    is_manual_connection           = false
  }

  private_dns_zone_group {
    name                 = "sql-dns-group"
    private_dns_zone_ids = [azurerm_private_dns_zone.sql.id]
  }

  tags = {
    Environment = "production"
  }
}

# Private DNS zone
resource "azurerm_private_dns_zone" "sql" {
  name                = "privatelink.database.windows.net"
  resource_group_name = azurerm_resource_group.main.name
}

# Link DNS zone to VNet
resource "azurerm_private_dns_zone_virtual_network_link" "sql" {
  name                  = "sql-dns-link"
  resource_group_name   = azurerm_resource_group.main.name
  private_dns_zone_name = azurerm_private_dns_zone.sql.name
  virtual_network_id    = azurerm_virtual_network.main.id
  registration_enabled  = false
}

# Disable public access
resource "azurerm_mssql_server" "main" {
  name                         = "myapp-sql"
  resource_group_name          = azurerm_resource_group.main.name
  location                     = azurerm_resource_group.main.location
  version                      = "12.0"
  administrator_login          = "sqladmin"
  administrator_login_password = var.sql_admin_password
  public_network_access_enabled = false
}

# Subnet for private endpoints (no NSG delegation issues)
resource "azurerm_subnet" "private_endpoints" {
  name                                      = "private-endpoints"
  resource_group_name                       = azurerm_resource_group.main.name
  virtual_network_name                      = azurerm_virtual_network.main.name
  address_prefixes                          = ["10.0.3.0/24"]
  private_endpoint_network_policies = "Enabled"
}

Cross-Region Private Endpoints

Private endpoints can connect to Azure services in different regions than the VNet. For example, a VNet in East US can have a private endpoint connecting to a Cosmos DB account in West Europe. This is useful for disaster recovery scenarios and accessing globally distributed services from a specific region.

bash
# Create a private endpoint in East US for a resource in West Europe
az network private-endpoint create \
  --name "cosmosdb-cross-region-pe" \
  --resource-group myapp-rg \
  --vnet-name eastus-vnet \
  --subnet private-endpoints-subnet \
  --private-connection-resource-id "/subscriptions/SUB_ID/resourceGroups/myapp-rg/providers/Microsoft.DocumentDB/databaseAccounts/mycosmosdb" \
  --group-id Sql \
  --connection-name "cosmosdb-pe-connection" \
  --location eastus

# Note: the resource (Cosmos DB) can be in any region
# The private endpoint and its IP are in the VNet's region

Hub-Spoke Integration

In hub-spoke network topologies, private endpoints are typically created in the hub VNet with centralized DNS zones. Spoke VNets use VNet peering to access the hub, and DNS zones are linked to all spoke VNets for name resolution.

hcl
# Centralized DNS in hub VNet
resource "azurerm_private_dns_zone_virtual_network_link" "spoke1" {
  name                  = "spoke1-link"
  resource_group_name   = azurerm_resource_group.hub.name
  private_dns_zone_name = azurerm_private_dns_zone.sql.name
  virtual_network_id    = azurerm_virtual_network.spoke1.id
  registration_enabled  = false
}

resource "azurerm_private_dns_zone_virtual_network_link" "spoke2" {
  name                  = "spoke2-link"
  resource_group_name   = azurerm_resource_group.hub.name
  private_dns_zone_name = azurerm_private_dns_zone.sql.name
  virtual_network_id    = azurerm_virtual_network.spoke2.id
  registration_enabled  = false
}

# DNS forwarder for on-premises resolution
resource "azurerm_linux_virtual_machine" "dns_forwarder" {
  name                = "dns-forwarder"
  resource_group_name = azurerm_resource_group.hub.name
  location            = azurerm_resource_group.hub.location
  size                = "Standard_B2s"
  admin_username      = "azureuser"
  network_interface_ids = [azurerm_network_interface.dns_forwarder.id]

  # Configure as DNS forwarder with conditional forwarding
  # for privatelink.* zones to 168.63.129.16
}

Troubleshooting Private Endpoints

Most private endpoint issues stem from DNS misconfiguration. Here is a systematic troubleshooting approach.

bash
# Step 1: Verify the private endpoint is in Approved state
az network private-endpoint show \
  --name sql-private-endpoint \
  --resource-group myapp-rg \
  --query 'privateLinkServiceConnections[0].privateLinkServiceConnectionState.status'
# Expected: "Approved"

# Step 2: Check the private IP assignment
az network private-endpoint show \
  --name sql-private-endpoint \
  --resource-group myapp-rg \
  --query 'customDnsConfigs[].ipAddresses[]' --output tsv
# Expected: 10.0.3.x (private IP in your subnet)

# Step 3: Verify DNS resolution from inside the VNet
# From a VM in the VNet:
nslookup myapp-sql.database.windows.net
# Expected: myapp-sql.privatelink.database.windows.net -> 10.0.3.x

# Step 4: Check if DNS zone is linked to the VNet
az network private-dns zone vnet-link list \
  --zone-name "privatelink.database.windows.net" \
  --resource-group myapp-rg \
  --query '[].{Name:name, VNet:virtualNetwork.id, Status:provisioningState}'

# Step 5: Test connectivity
# From a VM in the VNet:
# Test-NetConnection -ComputerName myapp-sql.database.windows.net -Port 1433
# Expected: TcpTestSucceeded: True

# Common issues:
# 1. DNS resolves to public IP -> DNS zone not linked to VNet
# 2. Connection timeout -> NSG blocking traffic to private endpoint subnet
# 3. "Pending" state -> Manual approval required on the service
# 4. Works from VNet but not on-premises -> DNS forwarding not configured

NSG and Security Considerations

By default, Network Security Groups (NSGs) do not apply to private endpoint traffic. To enable NSG filtering on private endpoint traffic, you must enable thePrivateEndpointNetworkPolicies property on the subnet. This is important for organizations that require microsegmentation.

bash
# Enable NSG enforcement on private endpoint subnet
az network vnet subnet update \
  --name private-endpoints \
  --vnet-name myapp-vnet \
  --resource-group myapp-rg \
  --private-endpoint-network-policies Enabled

# Create NSG rule to restrict private endpoint access
az network nsg rule create \
  --nsg-name pe-subnet-nsg \
  --resource-group myapp-rg \
  --name "allow-app-to-sql" \
  --priority 100 \
  --direction Inbound \
  --access Allow \
  --source-address-prefixes "10.0.1.0/24" \
  --destination-address-prefixes "10.0.3.0/24" \
  --destination-port-ranges 1433 \
  --protocol Tcp

Use Azure Policy for Enforcement

Use Azure Policy to enforce private endpoint usage across your organization. The built-in policy "Azure services should use Private Link" audits or denies resources without private endpoints. Combine this with "Configure services to use private DNS zones" to automate DNS configuration. This ensures no team accidentally creates PaaS resources with public endpoints.

Azure VNet Architecture PatternsAzure Networking Deep Dive

Key Takeaways

  1. 1Private Endpoints assign a private IP in your VNet to Azure PaaS services, eliminating public exposure.
  2. 2DNS configuration is the most critical and error-prone aspect of private endpoint setup.
  3. 3Each Azure service has a specific privatelink DNS zone name that must be created and linked.
  4. 4On-premises access requires DNS forwarding through a forwarder VM to Azure DNS (168.63.129.16).

Frequently Asked Questions

What is the difference between Service Endpoints and Private Endpoints?
Service Endpoints keep traffic on the Azure backbone but use the service's public IP. Private Endpoints assign a private IP in your VNet. Use Private Endpoints when you need DNS-level isolation, on-premises access via VPN/ExpressRoute, or compliance requirements that prohibit public endpoints.
Why does my application still connect via the public endpoint after creating a Private Endpoint?
DNS resolution is likely resolving to the public IP. Verify that the private DNS zone (e.g., privatelink.database.windows.net) is created and linked to your VNet. Test with nslookup from inside the VNet. The FQDN should resolve to the private IP assigned to the endpoint.

Written by CloudToolStack Team

Cloud engineers and architects with hands-on experience across AWS, Azure, and GCP. We write guides based on real-world production patterns, not just documentation rewrites.

Disclaimer: This guide is for educational purposes. Cloud services change frequently; always refer to official documentation for the latest information. AWS, Azure, and GCP are trademarks of their respective owners.