Private Endpoint DNS is one of those things that looks straightforward until it isn’t. The Azure docs cover the happy path well enough. What they don’t cover is what happens in a hub-and-spoke environment where your DNS lives in the hub and your workloads are spread across spokes.
This is the setup I run into most often in practice, and it has a few non-obvious gotchas.
The problem#
When you create a Private Endpoint, Azure creates a private DNS zone and links it to the VNet the endpoint lives in. In a flat network, that’s fine. In hub-and-spoke, your workloads in the spoke VNets won’t resolve the private IP — they’re not linked to that zone, and your central DNS forwarder in the hub doesn’t know about it either.
The result: traffic from spokes bypasses the private endpoint entirely and hits the public endpoint. You won’t always notice immediately, especially if you haven’t locked down public access yet.
What actually works#
There are two common approaches. Which one fits depends on how your DNS is set up.
Option 1: Link the private DNS zone to all spokes#
The simplest fix if you’re managing a small number of spokes. Link privatelink.blob.core.windows.net (or whichever
zone applies) to each spoke VNet.
az network private-dns link vnet create \
--resource-group rg-dns \
--zone-name privatelink.blob.core.windows.net \
--name link-spoke-01 \
--virtual-network spoke-vnet-01 \
--registration-enabled falseWorks, but doesn’t scale. Every new spoke needs a new link, and every new service needs a zone linked across all spokes. You end up maintaining a matrix of zones and VNets.
Option 2: Conditional forwarder on your DNS resolver#
The better approach for anything beyond a handful of spokes. Point your Azure DNS Private Resolver (or your custom DNS VM if you’re running one) at the privatelink zones using a conditional forwarding rule.
az dns-resolver forwarding-ruleset rule create \
--resource-group rg-dns \
--ruleset-name frs-hub \
--name rule-blob-private \
--domain-name "blob.core.windows.net." \
--target-dns-servers "[{ipAddress:'168.63.129.16',port:53}]"This routes all DNS queries for blob.core.windows.net through Azure’s internal resolver, which knows about your
private DNS zones. Spokes send DNS to the hub resolver, hub resolver forwards to Azure DNS, Azure DNS returns the
private IP.
Zone names by service#
The zone name differs per service. Common ones:
| Service | Private DNS Zone |
|---|---|
| Azure Blob Storage | privatelink.blob.core.windows.net |
| Azure Key Vault | privatelink.vaultcore.azure.net |
| Azure SQL | privatelink.database.windows.net |
| Azure Container Registry | privatelink.azurecr.io |
| Azure Service Bus | privatelink.servicebus.windows.net |
Full list is in the Microsoft docs — it’s long and occasionally changes when new services add Private Endpoint support.
Verifying it works#
Before you close the ticket, verify end-to-end from an actual spoke VM — not from the portal, and not from the hub.
# From a VM in the spoke
nslookup mystorageaccount.blob.core.windows.net
# Should return the private IP (10.x.x.x), not a public one
# If it returns something like 20.x.x.x you're still going publicIf nslookup returns a private IP, dig a bit deeper and confirm the storage account actually has public access disabled:
az storage account show \
--name mystorageaccount \
--query "networkRuleSet.defaultAction"
# Should return "Deny"Gotchas#
A few things that catch people out:
- Zone name for multi-region services: some services use a region-specific zone, not a global one. Azure SQL Managed Instance is a good example. Check before you configure.
- Split-horizon DNS: if you’re running your own DNS on-premises and forwarding to Azure, make sure the forwarder is also picking up the privatelink zones. On-premises DNS won’t automatically resolve private endpoints.
- NSG on the subnet: Private Endpoints ignore NSG rules on the subnet they’re deployed in. But other resources connecting to the endpoint are still subject to NSGs on their side.
In practice, the DNS resolver with conditional forwarding is the setup that actually holds up as the environment grows. The zone-linking approach turns into a maintenance burden faster than you’d expect.
