Securely Connecting Azure Services with a VNet and Private Endpoints
By default, most Azure PaaS services (Storage, SQL, Key Vault, Service Bus) are reachable through public endpoints, protected only by authentication and firewall rules. A Private Endpoint gives the service a private IP address inside your VNet, so traffic stays on the Microsoft backbone and you can turn public access off entirely.
The building blocks
- Virtual Network (VNet): your private network in Azure, divided into subnets and secured with NSGs, route tables, and optionally Azure Firewall.
- Private Endpoint: a network interface (NIC) in one of your subnets, mapped to one specific resource and sub-resource (for example, the blob service of one storage account). It is powered by Azure Private Link.
- Private DNS zone: makes the service's normal hostname (
mystorage.blob.core.windows.net) resolve to the private IP from inside your network. DNS is the most common cause of Private Endpoint problems. - VNet integration: lets an App Service or Function App make outbound calls into your VNet.
Inbound vs. outbound (a frequent point of confusion)
| Need | Feature |
|---|---|
| Reach a PaaS service privately (SQL, Storage, Key Vault) | Private Endpoint on that service |
| Let App Service/Functions call into the VNet | Regional VNet integration (outbound only) |
| Make the web app or function itself privately reachable | Private Endpoint on the app (inbound) |
An app typically needs both: VNet integration so its outbound traffic enters the VNet, and private endpoints on the services it calls.
How the traffic flows
App Service (VNet integration, snet-app)
│ 1. DNS: mystorage.blob.core.windows.net
│ → CNAME privatelink.blob... → Private DNS zone → 10.0.2.4
▼
VNet ──► Private Endpoint NIC (10.0.2.4, snet-pe) ──► Storage account (public access disabled)
Step-by-step setup
1. Create the VNet and subnets
az network vnet create -g my-rg -n my-vnet --address-prefixes 10.0.0.0/16
# Subnet for app outbound traffic (delegated to App Service)
az network vnet subnet create -g my-rg --vnet-name my-vnet -n snet-app \
--address-prefixes 10.0.1.0/24 --delegations Microsoft.Web/serverFarms
# Subnet for private endpoints
az network vnet subnet create -g my-rg --vnet-name my-vnet -n snet-pe \
--address-prefixes 10.0.2.0/24
Use a separate subnet for private endpoints, and size subnets for growth. A delegated integration subnet can't be shared with other resource types.
2. Create the Private Endpoint
az network private-endpoint create -g my-rg -n pe-storage-blob \
--vnet-name my-vnet --subnet snet-pe \
--private-connection-resource-id $(az storage account show -n mystorage -g my-rg --query id -o tsv) \
--group-id blob --connection-name storage-blob-conn
The --group-id selects the sub-resource. Storage has separate ones for blob, queue, table, file, and dfs, and each needs its own endpoint.
3. Set up private DNS
az network private-dns zone create -g my-rg -n privatelink.blob.core.windows.net
az network private-dns link vnet create -g my-rg \
-n link-my-vnet -z privatelink.blob.core.windows.net \
-v my-vnet -e false
# Automatically creates/maintains the A record for the endpoint
az network private-endpoint dns-zone-group create -g my-rg \
--endpoint-name pe-storage-blob -n default \
--private-dns-zone privatelink.blob.core.windows.net --zone-name blob
4. Close the public door
Creating the endpoint doesn't remove the public one. Disable it:
az storage account update -n mystorage -g my-rg --public-network-access Disabled
Most services have an equivalent setting (publicNetworkAccess, "Deny public network access", or firewall set to deny).
5. Connect the app's outbound traffic to the VNet
az webapp vnet-integration add -g my-rg -n my-app --vnet my-vnet --subnet snet-app
az webapp config set -g my-rg -n my-app --vnet-route-all-enabled true
Routing all outbound traffic through the VNet ensures private DNS and your NSG/firewall rules apply to everything the app calls. Without it, only private (RFC1918) destinations go through the VNet.
Common services: sub-resource and DNS zone
| Service | Group ID | Private DNS zone |
|---|---|---|
| Storage (Blob) | blob |
privatelink.blob.core.windows.net |
| Azure SQL | sqlServer |
privatelink.database.windows.net |
| Key Vault | vault |
privatelink.vaultcore.azure.net |
| Service Bus / Event Hubs | namespace |
privatelink.servicebus.windows.net |
| Cosmos DB (NoSQL) | Sql |
privatelink.documents.azure.com |
| App Service / Functions | sites |
privatelink.azurewebsites.net |
| Container Registry | registry |
privatelink.azurecr.io |
| Redis | redisCache |
privatelink.redis.cache.windows.net |
Zone names are standardized, and the current list is in the Azure docs ("Private endpoint DNS zone values"). Using the exact name matters, because the service's CNAME points to it.
Layers of network security
- Private Endpoints for inbound access to PaaS, with public access disabled.
- NSGs on subnets. By default, NSGs and UDRs don't apply to private endpoint traffic. Enable the subnet's
privateEndpointNetworkPoliciessetting if you want to enforce them. - Azure Firewall or an NVA for egress control: allow only the destinations and FQDNs the app needs, and block general internet access to reduce data exfiltration risk.
- Identity on top of the network: keep using managed identity and RBAC. Network isolation doesn't replace authentication, and a private endpoint without proper RBAC is still over-trusting.
- Private Link per resource, not per service: a private endpoint is tied to your specific storage account, so it can't be used to reach another tenant's account. This is a key exfiltration protection that service endpoints lack.
Private Endpoint vs. Service Endpoint
| Service Endpoint | Private Endpoint | |
|---|---|---|
| Target IP | Still the service's public IP (traffic routed over backbone) | Private IP in your VNet |
| Scope | Entire service type in a region | One specific resource |
| Reachable from on-premises | No | Yes (via VPN/ExpressRoute) |
| Exfiltration protection | Limited | Strong |
| DNS changes | None | Required (private DNS) |
| Cost | Free | Hourly charge plus data processed |
Service endpoints are simpler and free, but Private Endpoints are the recommended choice for stronger isolation and hybrid access.
Multi-VNet and hybrid networks
- Hub-and-spoke: put shared services, firewall, and DNS in a hub VNet and peer spokes to it. Private DNS zones are typically linked to the hub (and spokes), or centralized.
- On-premises access: connect via VPN Gateway or ExpressRoute. On-premises DNS must forward the
privatelinkqueries to Azure, typically through Azure DNS Private Resolver (or a DNS forwarder VM) in the VNet. Forward the public zone name (e.g.,blob.core.windows.net) to Azure DNS, not theprivatelinkzone directly. - VNet peering doesn't automatically share DNS. Each VNet that needs to resolve the private records must be linked to the private DNS zone or use a shared resolver.
- Use Azure Policy to enforce private endpoints and deny public access across subscriptions, and to auto-create DNS zone groups.
Service-specific gotchas
- Azure Functions: the function's host storage account needs private endpoints for blob, queue, table, and file (the file share is used on some plans), and you may need extra settings for content share routing. Check the current Functions networking docs for your plan.
- Service Bus: private endpoints require the Premium tier.
- Container Registry: Private Link requires the Premium tier.
- Key Vault: with public access disabled, only callers with a private path can reach it, including your pipelines. Make sure CI/CD agents run in the VNet (self-hosted agents or private agents) or use a deliberate exception.
- Azure SQL: private endpoints work with the Proxy/Redirect connection policy. Redirect needs additional ports reachable inside the VNet, so check your NSG rules.
- Application Insights / Azure Monitor: use an Azure Monitor Private Link Scope (AMPLS) to keep telemetry traffic private.
- Cross-subscription or cross-tenant endpoints: the connection may need to be approved on the target resource (manual approval flow).
- Deployment access: if you disable public access on an app's SCM site or a storage account, make sure your deployment path (private agents, VNet-based runners) can still reach it.
Troubleshooting checklist
- DNS first: from inside the VNet (Kudu console, a VM, or a container), run
nslookup mystorage.blob.core.windows.net. It must return the private IP (10.0.2.x). A public IP means the DNS zone link, zone group, or custom DNS forwarding is wrong. - Check the endpoint state: the private endpoint connection should be
Approved. - Check the VNet's DNS servers: if you use custom DNS, it must forward to Azure DNS (
168.63.129.16) or a Private Resolver. - Test connectivity:
tcpping(App Service Windows),curl, or Network Watcher → Connection troubleshoot and IP flow verify. - Review NSG, UDR, and firewall rules, especially if network policies for private endpoints are enabled.
- Verify the app is actually routing through the VNet: the integration subnet, route-all setting, and that the integration is connected.
- Confirm the public path is intentionally blocked (a
403from the service often means the request arrived over the public path, not the private one).
Good practices
- Define everything as code (Bicep or Terraform), including endpoints, DNS zones, and links, so environments match.
- Centralize private DNS zones (hub) and automate record creation with DNS zone groups or Azure Policy.
- Plan IP address space early. Overlapping ranges block peering and hybrid connectivity later.
- Disable public network access on every service that has a private endpoint, unless you have a documented exception.
- Combine private networking with managed identity, RBAC, Key Vault, and diagnostic logging to Log Analytics.
- Mind the cost: each endpoint has an hourly charge plus per-GB processing, and you need one per sub-resource. Consolidate where reasonable (for example, shared services in the hub).
Minimal secure pattern
- Create a VNet with separate subnets for app integration and private endpoints.
- Add VNet integration (with route-all) to the App Service or Function App.
- Create Private Endpoints for Storage, SQL, Key Vault, and Service Bus, each with the correct private DNS zone linked to the VNet.
- Disable public network access on those services.
- Authenticate with managed identity and RBAC, and put Front Door or Application Gateway (WAF) in front of the app for inbound traffic.
- Verify DNS resolves to private IPs, and monitor with Network Watcher and diagnostic logs.