Any Azure project with real security and isolation goals brings up private endpoints DNS sooner or later, and it reliably causes headaches. The documentation makes it look simple. In practice the app can't connect, nslookup hands back the public IP, and nobody can say where the DNS problem comes from. In most of the tenants I review, the private endpoint itself is fine. DNS misconfiguration is what eats the troubleshooting time.

Private DNS zones belong in one central place and not in every spoke

The first trap I keep running into is a private DNS zone (like privatelink.blob.core.windows.net) created separately in each spoke VNet. Sometimes I even find several zones with the same name in different VNets, left behind by copy-paste or a test that went sideways. That setup almost always produces inconsistent or broken name resolution. The approach that holds up over time is to create the private DNS zone once, centrally (usually in the hub VNet), and link it to every VNet that needs to resolve private endpoint names. Skip that, and clients in other VNets either resolve to public IPs or get no answer. Microsoft's documentation explains the basics well, but in real environments the zone linking step is where most things fall apart.

A classic example: the zone is linked to some networks but not all, or an old test zone is still hanging around. Depending on where the VM sits, the same storage account now resolves differently. A quick nslookup from an Azure VM usually exposes the problem:

nslookup storageaccountname.blob.core.windows.net

If that returns a public IP even though a private endpoint exists, your DNS setup is broken. The cause is nearly always a DNS zone that is missing, linked wrongly or duplicated. Users see inconsistent behavior, and troubleshooting turns into guesswork.

Azure DNS Private Resolver holds a hub-and-spoke design together

In hub-and-spoke setups I consider Azure DNS Private Resolver mandatory for central DNS governance. It connects the cloud networks to the on-premises DNS infrastructure. Through the inbound endpoint, your on-premises DNS servers (over VPN or ExpressRoute) can send queries into Azure. Through the outbound endpoint, Azure resources can resolve names in on-prem AD or other custom zones. Microsoft's overview is solid, though the tricky parts are in the details.

The practical setup looks like this. The Private Resolver goes into the hub, all spokes forward their DNS requests to it, and your on-prem DNS gets a conditional forwarder pointing at the inbound endpoint's IP. On-prem hosts can then resolve Azure-private names, and Azure workloads get consistent answers for everything, legacy on-prem domains included. When the wiring is right, nslookup returns the same private IP whether you run it on-prem or in Azure. When it isn't, you get public answers and strange connectivity problems.

Real-world tip: Stick to one centrally managed DNS zone. A single forgotten zone in a spoke can take over resolution for an entire subscription, so document every test and every cleanup without exception.

Azure Policy takes the manual work out of DNS

Once you have more than a handful of landing zones, creating and linking DNS zones by hand stops scaling. Azure Policy helps a lot here. It can create the DNS records and link the private DNS zone automatically whenever someone provisions a private endpoint. That cuts out human error and enforces compliance at the same time. The Cloud Adoption Framework from Microsoft offers good templates and design patterns for private endpoints DNS at scale. In my projects this kind of automation is now standard. For more on policy-driven landing zone governance, see my post on Azure landing zones.

Public network access and NSGs still need a final check

After everything has gone private, public network access has to be disabled on all relevant Azure resources. I see this step skipped far too often, and it leaves attack surface open for no reason. One toggle in the portal or the ARM template is enough to close it. Also look at how Network Security Groups (NSGs) and user defined routes (UDRs) behave on the subnet that hosts the private endpoint. Network policies for private endpoints are off by default, so if you need granular NSG/UDR control, you have to turn them on explicitly. Small settings like these often decide whether a setup is secure by default or quietly left open.

This simple diagram shows how the pieces of the DNS flow fit together:

flowchart TD
    OnPremDNS["On-Premises DNS"] --> CondFwd["Conditional Forwarder"]
    CondFwd --> InboundEP["Azure DNS Private Resolver (Inbound)"]
    InboundEP --> PrivateDNSZone["Private DNS Zone"]
    SpokeVM["Spoke VM"] --> HubDNS["Hub DNS Forwarder"]
    HubDNS --> OutboundEP["Azure DNS Private Resolver (Outbound)"]
    OutboundEP --> PrivateDNSZone
    PrivateDNSZone --> PrivateEP["Private Endpoint"]
    PrivateEP --> AzureResource["Azure Resource"]

Figure: Central DNS resolution for private endpoints with DNS Private Resolver in the hub.

I still think DNS is the real challenge

Several of the worst Azure outages I've seen turned out to be plain old DNS problems, and the business impact was very real. With private endpoints DNS, one small oversight can cost hours of downtime or expose something to the internet you didn't intend to. My advice is to document your DNS architecture, centralize as much as you can, and treat every leftover test zone as a production incident waiting to happen. Get this right and private endpoints finally do the isolation job they were built for, instead of generating frustration.