Azure Private Link: Fix Setup & Config Errors Fast
Why This Is Happening
I've seen this exact situation on dozens of Azure deployments: an engineer spins up Azure Private Link, connects a private endpoint to a Storage account or SQL Database, and then... nothing. The connection times out. Or DNS resolves to the wrong IP. Or the private endpoint shows as "Pending" in the portal for what feels like forever. It's maddening, especially when everything looks right in the configuration pane.
Here's the truth , Azure Private Link is one of those services that looks deceptively simple on the surface but has several moving parts that have to align perfectly. When one piece is off, the error messages Azure gives you are almost insultingly vague. "Connection failed" tells you nothing. "Endpoint not reachable" could mean five different things. I know this is frustrating, especially when it's blocking a production deployment.
So what actually goes wrong? Let's break it down honestly.
The core concept first: Azure Private Link lets your virtual network talk to Azure PaaS services , like Azure Storage, Cosmos DB, or SQL Database, without that traffic ever touching the public internet. Instead, it flows across Microsoft's backbone network through something called a private endpoint, which is just a network interface with a private IP address from your own VNet subnet. That's it. But when DNS, network policies, subnet configuration, or approval workflows go sideways, the whole thing breaks silently.
The most common culprits I see:
- DNS misconfiguration, your VM is still resolving the PaaS service's public FQDN to the public IP instead of the private endpoint IP. This is the #1 cause of "connection works but goes over internet" issues.
- Network policies blocking the endpoint, by default, certain network policies (like NSG enforcement) are disabled on private endpoint subnets, but if someone enabled them without configuring the right rules, traffic dies silently.
- Pending approval state, private endpoint connections require approval from the service owner. If you're connecting to a service in a different subscription or tenant, that approval step is manual and easy to miss.
- Subnet too small or lacking the right delegations, private endpoints need to land in a subnet, and that subnet needs to have the right configuration. Putting them in an overly restricted or shared subnet causes hard-to-diagnose failures.
- Missing Private DNS Zone linkage, you created a Private DNS Zone, but forgot to link it to the VNet where your workload lives. DNS queries never reach the private zone, so they fall back to public resolution.
Who runs into this? Mostly developers and architects who are setting up Azure Private Link for the first time, and also experienced teams migrating from VNet Service Endpoints (which work differently). If you're coming from Service Endpoints, the mental model shift trips people up, Private Link is not just a "better Service Endpoint," it's a fundamentally different connectivity mechanism.
The Quick Fix, Try This First
Before diving into a five-step overhaul, do this DNS check. It catches roughly 60% of Azure Private Link connectivity failures in my experience, and it takes under two minutes.
From a VM inside your VNet, open a PowerShell or Bash session and run an nslookup against your PaaS resource's FQDN. For example, if your storage account is myaccount.blob.core.windows.net:
nslookup myaccount.blob.core.windows.net
What you want to see is the FQDN resolving to a private IP address, something in your VNet's address space like 10.0.1.5. What you don't want to see is the public IP (something like 52.x.x.x or 20.x.x.x).
If you're getting the public IP back, your Private DNS Zone is either not created, not linked to the right VNet, or the A record inside it is wrong. Azure Private Link depends on a Private DNS Zone with the correct zone name for the service type, for example, privatelink.blob.core.windows.net for Blob Storage, linked to the VNet your workload is in.
Go to Azure Portal → Private DNS Zones, find the zone matching your service, click Virtual network links, and confirm your VNet is listed there with a status of Completed. If it's not there, click + Add, select your VNet, and save. Give it 60–90 seconds, then re-run the nslookup. Most of the time, that's the entire fix.
Also confirm the A record inside the zone points to the correct private IP. Go into the DNS zone, click Record sets, and find the A record for your resource. It should match the private IP shown on your private endpoint's network interface.
The first thing to confirm is that your private endpoint connection is actually in an Approved state, not Pending, Rejected, or Disconnected. This matters more than people realize. A private endpoint in "Pending" state will never pass traffic, period.
Navigate to Azure Portal → Private endpoints (search for it in the top bar). Click on your endpoint and look at the Connection tab. You'll see a field labeled Connection state. It should read Approved.
If it says Pending, there are two scenarios:
- Same subscription: Go to the target resource (e.g., the Storage account or SQL Server), navigate to Networking → Private endpoint connections, find the pending connection, select it, and click Approve. Type a description if prompted and confirm.
- Cross-subscription or cross-tenant: The resource owner in the other subscription has to do the approval. They need to go to their resource's networking settings. You can also approve via PowerShell if you have the right RBAC role on the resource:
$pe = Get-AzPrivateEndpointConnection -ResourceGroupName "rg-name" `
-ServiceName "your-service-name" `
-Name "connection-name"
Approve-AzPrivateEndpointConnection -ResourceId $pe.Id `
-Description "Approved by network team"
After approval, go back to the private endpoint's Connection tab and refresh. The status should switch to Approved** within 30–60 seconds. If it stays in Pending after 5 minutes, check that the approver has the correct RBAC permissions on the target resource, at minimum they need the Network Contributor role or a custom role that includes Microsoft.Network/privateEndpointConnections/write.
Once you see Approved, move on to the DNS check in step 2.
This is the step where most Azure Private Link setups actually break. Even if the private endpoint is approved and the network interface has a private IP, if DNS doesn't route queries to that IP, your application will still try to connect over the public internet.
Azure Private Link works with specific Private DNS Zone names depending on the service. These are not arbitrary, they have to match exactly. Here are the most common ones:
Azure Blob Storage: privatelink.blob.core.windows.net
Azure SQL Database: privatelink.database.windows.net
Azure Cosmos DB: privatelink.documents.azure.com
Azure Key Vault: privatelink.vaultcore.azure.net
Azure Container Reg.: privatelink.azurecr.io
Go to Azure Portal → Private DNS Zones and confirm a zone with the correct name exists. Then click into it and do two things:
A) Check the A record: Under Record sets, look for an A record matching your resource name (e.g., myaccount for a storage account named myaccount). The IP value should match the private IP on your endpoint's NIC. If it's wrong or missing, click + Record set, set the name to just the resource short-name (not the FQDN), type A, TTL 10 seconds for testing, and enter the correct private IP from your endpoint's network interface.
B) Check the VNet link: Under Virtual network links, your VNet must be listed with registration status showing Completed. If it's not linked, click + Add, name the link, select your VNet, leave auto-registration off for private endpoint DNS zones, and save. Wait 60 seconds and test DNS again from a VM in that VNet.
One important nuance: if you have a hub-and-spoke topology, the DNS zone needs to be linked to the VNet where your DNS resolver sits, not necessarily every spoke. Verify your DNS forwarding rules if you use Azure Firewall DNS proxy or a custom DNS server.
Here's something that catches people off-guard: by default, Network Security Group (NSG) rules and User-Defined Routes (UDRs) do not apply to private endpoints inside a subnet. But if someone has explicitly enabled network policies on that subnet, those policies kick in, and if the NSG doesn't have the right allow rules, traffic gets dropped.
Check the current policy state with PowerShell:
$vnet = Get-AzVirtualNetwork -Name "your-vnet-name" -ResourceGroupName "your-rg"
$subnet = $vnet.Subnets | Where-Object { $_.Name -eq "your-subnet-name" }
$subnet.PrivateEndpointNetworkPolicies
If this returns Enabled, network policies are active on the subnet. This isn't necessarily wrong, you might want NSG control over private endpoint traffic, but you need to make sure the associated NSG actually allows your traffic.
To disable network policies and go back to the default (open) behavior, run:
$subnet.PrivateEndpointNetworkPolicies = "Disabled"
Set-AzVirtualNetwork -VirtualNetwork $vnet
Or via Azure CLI:
az network vnet subnet update \
--name your-subnet-name \
--resource-group your-rg \
--vnet-name your-vnet-name \
--disable-private-endpoint-network-policies true
If you want to keep policies enabled (which is valid and sometimes required by your security team), make sure your NSG has an inbound rule allowing traffic from the source subnet to the private endpoint's private IP on the correct port, for example, port 1433 for SQL, port 443 for Storage HTTPS, etc. Use the Azure Monitor Network Insights or NSG flow logs to confirm whether packets are hitting the NSG and what action is being taken before assuming this is or isn't the problem.
Once DNS and network policies look correct, use Azure Network Watcher's Connection troubleshoot tool to actually test the path from your VM to the private endpoint IP. This tool gives you a definitive pass/fail with hop-by-hop analysis, much better than guessing.
In the Azure Portal, search for Network Watcher. Under Network diagnostic tools, click Connection troubleshoot.
Fill in the fields:
- Source: Select your virtual machine (the one having connectivity issues)
- Destination type: Choose "Specify manually" and enter the private IP of your private endpoint (you can find this on the endpoint's Overview → Network interface)
- Protocol: TCP
- Destination port: The appropriate port for your service (443 for Storage, 1433 for SQL)
Click Check and wait 60–90 seconds for the result. If it shows Reachable with a low latency number, your network path is fine and the issue is likely in the application layer or DNS (go back to Step 2). If it shows Unreachable, the output will tell you at which hop the packet dies, whether it's an NSG, a UDR routing to the wrong next hop, or a gateway misconfiguration.
Pay attention to the hop count. A legitimate private endpoint path within the same region should have very few hops, usually 1–3. If you see traffic being routed out through an NVA or Azure Firewall that's blocking it, that's your culprit. Check the UDR table on the private endpoint's subnet and the source VM's subnet to make sure there's no route forcing traffic to a forced-tunnel or NVA unnecessarily.
Once Azure Private Link is working, you should verify it's actually being used, not just that connectivity tests pass. Azure Private Link has native integration with Azure Monitor, and you can pull data-processed metrics that confirm traffic is flowing through the private endpoint rather than the public endpoint.
Go to Azure Portal → Private endpoints → [your endpoint] → Metrics. Add a metric chart for Bytes In and Bytes Out. If traffic is flowing, you'll see non-zero values. If both stay at zero even when your application is running, that's a signal the application isn't actually routing through the private endpoint, often because its connection string still references the public FQDN and the client is resolving it to the public IP from outside the VNet.
To set up persistent monitoring, navigate to Diagnostic settings on the private endpoint and configure a destination, either a Log Analytics Workspace, a Storage Account for archival, or an Event Hub for streaming to a SIEM:
az monitor diagnostic-settings create \
--name "pe-diagnostics" \
--resource "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Network/privateEndpoints/{pe-name}" \
--logs '[{"category": "AuditEvent","enabled": true}]' \
--metrics '[{"category": "AllMetrics","enabled": true}]' \
--workspace "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{workspace-name}"
For the Private Link service side (if you're operating a service behind a Standard Load Balancer), you can also track NAT port availability, a metric that becomes critical at scale. If NAT ports run out, new connections fail. Set an alert threshold at 75% NAT port utilization so you're never caught off guard. With monitoring in place, you have visibility into whether Azure Private Link is actually doing what you think it's doing.
Advanced Troubleshooting
If you've gone through all five steps and things still aren't working, we're in deeper-water territory. Here's what to check next.
Azure Private Link in Hub-and-Spoke Topologies
This is where enterprise deployments get complicated fast. If you're in a hub-and-spoke architecture, common in large organizations, your DNS resolution path matters enormously. Typically, DNS queries from spoke VNets get forwarded to a DNS server or Azure Firewall in the hub VNet. If the Private DNS Zone is only linked to the hub VNet, spokes won't get private resolution unless DNS forwarding is set up correctly to pass queries to Azure's resolver at 168.63.129.16.
In Azure Firewall DNS proxy scenarios, enable the DNS proxy on Azure Firewall and set your VMs' DNS server to the Firewall's private IP. Then create a DNS forwarding rule that routes queries for privatelink.blob.core.windows.net (or whichever zone) to 168.63.129.16. Finally, link the Private DNS Zone to the hub VNet. This chain, VM → Firewall DNS proxy → Azure resolver → Private DNS Zone, is the supported pattern for hub-and-spoke and is documented in Microsoft's Private Endpoints DNS integration guide.
Cross-Region Private Link Connectivity
One of Azure Private Link's genuinely useful features is that it works across regions. Your VNet in East US can connect to a service behind Private Link in West Europe. Traffic flows across the Microsoft backbone, no public internet exposure. But DNS becomes even more critical here because public DNS for a resource in another region will definitely return a public IP. Make sure the Private DNS Zone A record is correct and the VNet link covers the region where your consumer workload lives.
Private Link Service (Custom Services Behind SLB)
If you're not just consuming a PaaS service but actually publishing your own service via Azure Private Link Service, putting your workload behind a Standard Load Balancer and exposing it via Private Link, there are additional failure modes. The most common: your Standard Load Balancer's frontend IP is not configured for Private Link, or the NAT IP configuration for the Private Link Service is missing or using the wrong subnet. Check the Private link service resource in the portal and confirm the NAT IP configurations tab shows at least one IP mapped to a subnet in the provider VNet. Without a NAT IP, the service cannot establish connections.
Event Viewer and Network Trace Analysis
On Windows VMs, if you're troubleshooting a specific application failing to connect through a private endpoint, grab a Netsh trace during the failure:
netsh trace start capture=yes tracefile=C:\temp\pe-trace.etl
# Reproduce the failure
netsh trace stop
Open the ETL file in Microsoft Network Monitor or Wireshark (with etl2pcapng conversion) and look at what IP address the TCP SYN is going to. If it's the public IP, DNS is the problem. If it's the correct private IP but the SYN is getting no response or an RST, you're looking at NSG or routing.
Prevention & Best Practices
Getting Azure Private Link working is one thing. Keeping it working, especially across team changes, subscription reorganizations, and service updates, is another. These are the practices I'd put in every Azure landing zone by default.
Always deploy Private DNS Zones through Infrastructure as Code. Whether you're using Bicep, Terraform, or ARM templates, the DNS zone, VNet links, and A records should all be declared as code. When you do it manually in the portal, someone will eventually delete or modify the DNS zone, the VNet link will drift, and you'll spend hours debugging something that should never have broken. IaC makes the correct state reproducible and auditable.
Use a dedicated subnet for private endpoints. Don't co-locate private endpoints with VMs or other resources in the same subnet. Give them their own /27 or /28. This keeps your NSG rules clean and makes it easy to apply a specific policy to endpoints without affecting other workloads. The subnet doesn't need a large address space, private endpoints are lightweight, but isolation matters for security and troubleshooting.
Set up Azure Policy to enforce Private Link. There are built-in Azure Policy definitions that deny PaaS service creation without a private endpoint, or that audit PaaS services accessible from the public internet. Apply these at the management group level so every new subscription in your organization starts with the right guardrails. Policy IDs like 8b0323be-cc25-4b61-935d-002c3798c6ea (Deny public access to Azure SQL) are a good starting point.
Test DNS resolution from every consumer VNet, not just one. In multi-VNet environments, DNS works differently in each VNet depending on which DNS server is configured and which VNet links exist. A setup that resolves correctly from VNet A may fail silently from VNet B. Automate a DNS health check that runs from representative VMs in each VNet and alerts if the resolution target changes from private to public IP.
Monitor NAT port exhaustion on Private Link services. If you're running a high-traffic service behind Azure Private Link Service, NAT port availability is a hard limit. Configure Azure Monitor alerts on the NAT port availability metric, and plan for horizontal scaling of NAT IP configurations before you hit the ceiling.
- Run
nslookup [service-fqdn]from inside the VNet immediately after setup, confirm private IP before handing off to app teams - Tag all private endpoints and DNS zones with the same environment/project tags so you can track them together in cost and compliance reports
- Use Azure Policy's "deny public network access" definitions to prevent PaaS services from being accidentally left public after a Private Link deployment
- Document your Private DNS Zone → VNet link mapping in a runbook, DNS zone drift is the #1 cause of post-deployment Azure Private Link outages I've seen
Frequently Asked Questions
What is Azure Private Link and how is it different from VNet Service Endpoints?
Azure Private Link lets your VNet access Azure PaaS services, like Storage, SQL Database, or Cosmos DB, through a private endpoint, which is a real network interface with a private IP address inside your VNet. Traffic never touches the public internet; it flows across Microsoft's backbone network instead. VNet Service Endpoints, by contrast, extend your VNet's identity to the PaaS service but the traffic still goes over the Azure backbone through the service's public IP. The practical difference: with Private Link, the service gets a private IP and DNS can resolve to it privately, making the connectivity truly invisible from the public internet. Private Link also works across VNet peering, on-premises over ExpressRoute or VPN, and across Azure regions, Service Endpoints don't do cross-region.
What is a private endpoint exactly, is it the same as Private Link?
No, they're related but different things. A private endpoint is the actual network interface, a NIC with a private IP from your subnet, that connects to a specific instance of a PaaS resource. Azure Private Link is the broader platform and service that makes private endpoints possible. Think of Private Link as the highway system and the private endpoint as the specific on-ramp your VNet uses. Each private endpoint is mapped to one specific PaaS resource instance (like one storage account), not the entire service. That mapping is what protects you from data exfiltration, you can't accidentally reach another customer's storage account through the same endpoint.
My private endpoint shows "Pending", how do I approve it?
A "Pending" state means the service owner hasn't approved the connection request yet. If the target resource is in the same subscription as you, go to that resource in the Azure Portal (e.g., the Storage account or SQL Server), open Networking → Private endpoint connections, find the pending entry, select it, and click Approve. If the resource is in a different subscription or a different Microsoft Entra tenant, the resource owner in that subscription needs to do the approval, you can't do it from your side alone. You can also approve programmatically using PowerShell's Approve-AzPrivateEndpointConnection cmdlet if you have the right permissions. Once approved, the status updates within about a minute.
Does Azure Private Link work across regions and on-premises networks?
Yes, and this is one of its most useful features. A VNet in one Azure region can connect to a service behind Private Link in a different region, traffic flows across Microsoft's global backbone, never the public internet. For on-premises connectivity, you can reach private endpoints over ExpressRoute private peering or site-to-site VPN tunnels. You don't need ExpressRoute Microsoft peering (which routes over public IPs) to reach Private Link resources. The key thing to get right for on-premises or cross-region access is DNS, your on-premises DNS resolver needs to forward queries for the Private Link DNS zones to Azure's resolver at 168.63.129.16, either directly or via a DNS forwarder in Azure.
How do I know my traffic is actually going through the private endpoint and not the public internet?
Two quick checks: First, from a VM inside your VNet, run nslookup [your-service-fqdn], if it resolves to a private IP (in your VNet's address space), DNS is correct. Second, go to Azure Portal → Private endpoints → [your endpoint] → Metrics and look at the Bytes In / Bytes Out metrics, if there's traffic flowing through the endpoint while your app is running, it confirms usage. You can also use Azure Network Watcher's Connection troubleshoot tool to trace the actual path from your VM to the private endpoint IP. If the application's connection string still uses the public FQDN and the app is running outside the VNet, it will bypass the private endpoint entirely regardless of your network config.
What Private DNS Zone names does Azure Private Link use for different services?
Each Azure PaaS service has a specific, required Private DNS Zone name that you have to use exactly, there's no flexibility here. The most commonly used ones are: privatelink.blob.core.windows.net for Azure Blob Storage, privatelink.database.windows.net for Azure SQL Database, privatelink.documents.azure.com for Cosmos DB, privatelink.vaultcore.azure.net for Azure Key Vault, and privatelink.azurecr.io for Azure Container Registry. Microsoft maintains a full list of Private DNS Zone values in the official documentation, the table is quite long since every PaaS service has its own zone name, and some services like Storage have multiple zones for different endpoints (blob, file, queue, table). Always check the official Private DNS zone values reference before creating zones manually to avoid typo-related DNS failures.