Azure Networking

Azure Private Endpoint vs Service Endpoint: Secure PaaS Access

Quick take: a Service Endpoint teaches an Azure PaaS firewall to recognise traffic from your subnet over the Azure backbone — but the service still has a public IP and is still reachable from the internet by anyone the firewall allows. A Private Endpoint brings the PaaS service into your VNet as a private IP on a NIC you own, so traffic never touches a public IP and the public endpoint can be turned off entirely. For regulated or sensitive data the answer is almost always Private Endpoint; Service Endpoint is the cheaper, coarser control you reach for when public exposure is acceptable and you only need to pin the route.

A bank ran its payment-reconciliation jobs against Azure Storage over the public endpoint because “the connection string was encrypted and we use HTTPS.” Their security review rejected the design on two counts that TLS does nothing about: the storage account still had a routable public IP that anyone on the internet could attempt to reach (the account key or a leaked SAS would be enough), and the egress path from the subnet to Storage left the customer’s address space. Encryption protects the bytes in flight; it does not reduce the attack surface or stop data exfiltration to an attacker-controlled storage account in another tenant. Private Endpoint closed both gaps in one move — the account got a private IP inside the VNet, public network access was set to Disabled, and a Private Link policy on the subnet blocked any attempt to reach Storage accounts the team didn’t own. The route stayed on the Microsoft backbone, and the public surface dropped to zero.

This article is the full decision guide a senior network architect actually uses to choose between the two — not “Private Endpoint is better” hand-waving, but option-by-option: what each mechanism is, what it changes about routing, DNS, firewalls and identity, what it costs, where each one quietly fails, and the exact az and Bicep to stand each up for Storage, SQL Database and Key Vault. Because you will return to this mid-design and mid-incident, the comparison grids, the DNS-zone table, the NSG/UDR interaction, the cost model and a symptom-to-fix playbook are all laid out as scannable tables. Read the prose once; keep the tables open when the build pipeline says Name or service not known at 9pm.

By the end you will stop conflating the two. You will know that a Service Endpoint is a route + a firewall identity and a Private Endpoint is a private IP + a DNS problem you must solve; that the single most common Private Endpoint failure is DNS still resolving to the public IP; that Service Endpoints are free and Private Endpoints are billed per hour and per gigabyte; and that “we disabled public access” only means something with a Private Endpoint, never with a Service Endpoint.

What problem this solves

Azure PaaS services — Storage, SQL Database, Key Vault, Cosmos DB, Service Bus, App Service and dozens more — are born with a public endpoint: a fully-qualified domain name that resolves to a Microsoft-owned public IP, reachable from anywhere on the internet, gated only by the service’s own authentication and an optional service firewall. That default is wonderful for getting started and unacceptable for a great many production workloads. A storage account holding patient records, a SQL database with cardholder data, a Key Vault full of signing keys — none of these should present a routable public IP to the internet, and none of their traffic should leave your network’s address space on the way out.

What breaks without a deliberate choice here: a compliance auditor flags that mystorage.blob.core.windows.net answers from the public internet (a finding, regardless of firewall rules, because the surface exists); a misconfigured firewall rule or a leaked SAS token lets an attacker reach the account from outside; a malicious insider copies a database to a storage account in their own subscription because nothing on the network stops egress to arbitrary PaaS endpoints; or a “private” design turns out to still resolve to the public IP because nobody wired up Private DNS, so the traffic was never private at all. These are not theoretical — the last one is the single most common real-world Private Endpoint defect, and it fails silently (it works, just not privately).

Who hits this: anyone running regulated workloads (healthcare, finance, government), anyone whose security baseline mandates “no public endpoints on data services,” anyone doing data-exfiltration prevention, and — increasingly — every enterprise landing zone, because the platform team has standardised on private-only PaaS. The two tools Azure gives you are Service Endpoint (cheap, route-level, leaves the public IP) and Private Endpoint / Private Link (a private IP in your VNet, public access disablable). They are not interchangeable, they are frequently confused, and choosing wrong either over-spends or under-secures. This article is about choosing right, end to end.

To frame the whole field before the deep dive, here is what each mechanism actually changes about your network, side by side:

Dimension Public endpoint (default) Service Endpoint Private Endpoint
Service has a public IP? Yes Yes (unchanged) Yes, but can be disabled
Where the service’s IP resolves Public IP Public IP Private IP in your subnet
Path off the subnet Internet (public) Azure backbone Azure backbone
How the service trusts you Auth + optional firewall Subnet identity in the firewall The NIC is inside your network
Can you turn off public access? n/a No — firewall narrows, never closes Yes (Disabled)
Reachable from on-prem? Yes (over internet) No (Service Endpoints are VNet-local) Yes over ExpressRoute/VPN
Stops data exfiltration to other accounts? No Partially (service-side only) Yes with Private Link policies
Cost Free Free Hourly + per-GB
You must solve DNS? No No Yes (the whole game)

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand Azure VNet fundamentals — a virtual network is your private address space, carved into subnets, with Network Security Groups (NSGs) filtering traffic and route tables (UDRs) steering it; if those are fuzzy, read Azure Virtual Network: Subnets, NSGs, and Routing first. You should know that PaaS services have a service firewall (the “Networking” blade that lets you allow selected networks/IPs), how to run az in Cloud Shell, and that DNS resolution turns a name like mystorage.blob.core.windows.net into an IP. Basic familiarity with managed identity and PaaS authentication helps but isn’t required.

This sits in the Networking & PaaS security track. It is the practical companion to Azure Private Link & Private DNS for PaaS, which goes deeper on the DNS architecture and at-scale hub-and-spoke patterns; this article is the choice between the two endpoint types and the per-service mechanics. It pairs with Azure Key Vault: Secrets, Keys & Certificates (Key Vault is a prime Private Endpoint target) and Azure Storage Account Fundamentals (Storage has the richest sub-resource model). When a private path breaks, Troubleshooting Azure Storage: 403, Firewall, Private Endpoint, RBAC & SAS and Troubleshooting Azure VNet Connectivity are the playbooks. If you front PaaS with a gateway, Application Gateway with WAF & end-to-end TLS and Azure Load Balancer Standard: outbound rules & SNAT are adjacent.

A quick map of who owns and confirms what, so you escalate to the right person:

Layer What lives here Who usually owns it Failure it causes
VNet / subnet Address space, delegation, policies Network team Subnet too small for endpoints; policy missing
NSG / UDR Traffic filter + route steering Network / SecOps Egress blocked; 0.0.0.0/0 swallows the route
Private DNS zone Name → private IP records Platform / DNS team FQDN still resolves public (the #1 bug)
Service firewall Allow rules (Service Endpoint, IPs) Service owner 403 from the service; rules don’t apply
Private Endpoint NIC Private IP into the subnet App + network Wrong groupId; approval pending
On-prem DNS forwarder Conditional forward to Azure DNS On-prem / network Branch can’t resolve the private name

Core concepts

Six mental models make every later decision obvious. The fastest way to never confuse these two again is to internalise what each one is: a Service Endpoint is a route plus an identity; a Private Endpoint is a private IP plus a DNS problem.

A Service Endpoint changes the route off your subnet — nothing arrives. When you enable a Service Endpoint (e.g. Microsoft.Storage) on a subnet, Azure injects optimised routes so that traffic from that subnet to that service travels over the Azure backbone instead of egressing to the internet, and — critically — the packets now carry the subnet’s identity. The PaaS firewall can then say “allow traffic from subnet X of VNet Y.” The service keeps its public IP; the FQDN still resolves to that public IP; nothing is placed in your VNet. You are narrowing who may reach the still-public service and pinning the route, not making the service private.

A Private Endpoint puts the service inside your VNet as a private IP. A Private Endpoint is a network interface (NIC) the platform creates in your subnet, assigned a private IP from your address space, that maps — via Private Link — to a specific instance of a PaaS service (this exact storage account, this SQL server). Traffic to that private IP is proxied over Private Link to the service. Now the service is reachable at an IP you own, on the backbone, and you can set the service’s public network access to Disabled so the public IP stops answering entirely. The service is, for all practical purposes, plugged into your datacenter network.

The sub-resource (groupId) is which “face” of the service you connect. A single PaaS resource often exposes several endpoints — a storage account has blob, file, queue, table, dfs, web; a SQL logical server has sqlServer. A Private Endpoint targets one sub-resource, identified by its groupId (also called the target sub-resource). One private IP per sub-resource: if your app uses both Blob and File on the same account, that’s two Private Endpoints (and two DNS zones). Getting the groupId wrong is a common setup error — the endpoint comes up but points at the wrong face.

DNS is the entire Private Endpoint game. Creating the NIC does nothing useful until the FQDN resolves to its private IP. Azure does not rewrite the public DNS record; instead the service’s hostname follows a CNAME chain to a privatelink zone (e.g. mystorage.blob.core.windows.netmystorage.privatelink.blob.core.windows.net). You host that privatelink.* zone as an Azure Private DNS zone, link it to your VNet, and put an A record mapping the name to the private IP (the private DNS zone group on the endpoint does this automatically). Skip this and resolution falls through to the public IP — the connection still “works,” over the public path, which is the silent failure that fails audits.

Service Endpoints are VNet-local; Private Endpoints reach everywhere. A Service Endpoint only affects traffic originating in the VNet subnet where it’s enabled — on-prem machines over VPN/ExpressRoute cannot use it (their traffic isn’t from that subnet). A Private Endpoint’s private IP is reachable from anywhere that can route to your VNet, including on-premises over ExpressRoute or VPN (with DNS forwarding configured). If branch offices or a datacenter must reach the PaaS privately, Service Endpoint cannot help; Private Endpoint can.

Disabling public access only means something with a Private Endpoint. With a Service Endpoint the service firewall is set to “selected networks” — the public IP still exists and still answers anyone the firewall allows; you have narrowed, not closed, the door. Only a Private Endpoint lets you set publicNetworkAccess = Disabled, after which the public endpoint returns an error to all callers and the only way in is the private IP. “We turned off the public endpoint” is a true statement exactly once you have a Private Endpoint.

The vocabulary in one table

Pin down every moving part before the deep sections. The glossary repeats these for lookup; this is the model side by side:

Concept One-line definition Applies to Why it matters
Service Endpoint Subnet-scoped route + identity to a PaaS service over the backbone Subnet + service firewall Cheap, route-level; leaves public IP
Private Endpoint A NIC with a private IP in your subnet mapping to a PaaS instance Subnet + service instance Private IP; lets you disable public
Private Link The platform service that proxies the private IP to the PaaS instance Under a Private Endpoint The plumbing behind the private IP
privatelink.* DNS zone The Azure Private DNS zone holding the private A records DNS Without it, names resolve public
Private DNS zone group Endpoint feature that auto-creates the A record in the zone On the Private Endpoint Automates the #1 manual step
groupId / sub-resource Which face of the service (blob, file, sqlServer…) Private Endpoint target One endpoint per sub-resource
publicNetworkAccess Service-level switch for the public endpoint The PaaS resource Disabled = private-only
Service Endpoint Policy Restricts which accounts a subnet may reach via SE Subnet (Storage) Exfiltration control for SE
Private Link Policy / network policies NSG/UDR enforcement on the PE subnet; exfiltration guard Subnet Controls PE traffic + reach
Connection approval Owner of the PaaS resource approves the PE link Cross-tenant/-sub PE Pending state blocks traffic
NAT / SNAT Outbound address translation to a shared IP Egress path PE bypasses SNAT to that target

Service Endpoint, option by option

A Service Endpoint is the lighter tool. It is enabled per subnet, per service and is free. It does exactly two things: it makes traffic from the subnet to that service take the Azure backbone (an optimised system route), and it stamps that traffic with the subnet’s identity so the PaaS firewall can allow it specifically. It does not create any resource in your VNet, does not change DNS, and does not disable the public endpoint.

Enabling a Service Endpoint

You enable the service endpoint on the subnet and then add the subnet to the service’s firewall. Here it is for a subnet reaching Storage:

# 1) Turn on the Storage service endpoint for the subnet
az network vnet subnet update \
  --vnet-name vnet-app --name snet-workload --resource-group rg-net \
  --service-endpoints Microsoft.Storage

# 2) Lock the storage account to "selected networks" and allow that subnet
SUBNET_ID=$(az network vnet subnet show -g rg-net --vnet-name vnet-app -n snet-workload --query id -o tsv)
az storage account update -n mystorageacct -g rg-data --default-action Deny
az storage account network-rule add -n mystorageacct -g rg-data --subnet "$SUBNET_ID"
resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
  parent: vnet
  name: 'snet-workload'
  properties: {
    addressPrefix: '10.10.1.0/24'
    serviceEndpoints: [
      { service: 'Microsoft.Storage' }      // backbone route + subnet identity
      { service: 'Microsoft.KeyVault' }
      { service: 'Microsoft.Sql' }
    ]
  }
}

resource sa 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: 'mystorageacct'
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {
    networkAcls: {
      defaultAction: 'Deny'                 // deny by default
      virtualNetworkRules: [
        { id: subnet.id, action: 'Allow' }  // allow this subnet via the service endpoint
      ]
    }
  }
}

After this, mystorageacct.blob.core.windows.net still resolves to a public IP, the account is still discoverable on the internet, but only callers from the allowed subnet (or allowed IP ranges) get past the firewall. That is the whole mechanism — narrowing, on a still-public service.

The Service Endpoint option matrix

Every knob a Service Endpoint exposes, what it does, and the gotcha:

Setting / control What it does Default When to set it Trade-off / limit
serviceEndpoints on subnet Adds backbone route + subnet identity for a service none Whenever you’ll firewall a PaaS to this subnet Subnet-local only; no on-prem reach
Service value (e.g. Microsoft.Storage) Which PaaS family the SE covers n/a Per service you access Not every service supports SE
Service firewall defaultAction Allow/Deny baseline on the PaaS Allow (open) Deny to enforce the SE Allow makes the SE pointless
virtualNetworkRules Allow specific subnets none Add each allowed subnet Per-subnet; cap on rules
Service Endpoint Policy Restrict which Storage accounts the subnet may reach none Exfiltration control Storage only; extra object
Microsoft.Storage.Global vs regional Cross-region SE for Storage regional Reaching Storage in another region Newer; check service support
Cross-region access SE firewall rule in another region blocked Same-region by default Requires global SE variant

What Service Endpoints cannot do

The limits are the reason Private Endpoint exists. Enumerated so you don’t discover them in production:

Limitation Consequence The real fix
Service keeps a public IP Attack surface and audit finding remain Private Endpoint + disable public
Can’t disable public access “Selected networks” still answers allowed callers publicly Private Endpoint
VNet-local only On-prem over VPN/ExpressRoute can’t use it Private Endpoint
No DNS change Name resolves to the public IP Private Endpoint (with Private DNS)
Coarse identity (subnet) Can’t scope to a single resource neatly Private Endpoint targets one instance
Limited exfiltration control Subnet can still reach any account of that service (unless SE Policy) SE Policy (Storage) or Private Endpoint
Not every PaaS supports it Some services are Private Endpoint-only Use Private Endpoint there

Service Endpoint Policies — the exfiltration guard for Storage

A plain Service Endpoint lets the subnet reach any storage account on the backbone, including one in an attacker’s subscription — so it is not an exfiltration control by itself. Service Endpoint Policies narrow this: you attach a policy to the subnet that lists exactly which storage accounts (or which resource groups/subscriptions) the subnet is permitted to reach via the Service Endpoint. Outside the list, the backbone route is refused.

# Restrict the subnet to only the listed storage accounts via the service endpoint
az network service-endpoint policy create -g rg-net -n sep-storage-allow
az network service-endpoint policy-definition create -g rg-net \
  --policy-name sep-storage-allow -n allow-prod \
  --service Microsoft.Storage \
  --service-resources /subscriptions/<sub>/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/mystorageacct
az network vnet subnet update -g rg-net --vnet-name vnet-app -n snet-workload \
  --service-endpoint-policy sep-storage-allow

When each Service Endpoint construct applies, in one grid:

Construct Scope Controls Use it to
Service Endpoint Subnet → service family Route + identity Pin route, firewall PaaS to subnet
VNet firewall rule Service ← subnet Inbound allow Let the subnet past the PaaS firewall
Service Endpoint Policy Subnet → specific accounts Egress allow-list Stop exfiltration to foreign accounts
defaultAction: Deny Service Baseline Make “selected networks” actually deny

Which services support Service Endpoint vs Private Endpoint

Not every PaaS supports both — some are Private Endpoint-only, and that alone can force your hand. This is the support matrix for the services you’ll meet most (always confirm current support in the portal, as coverage expands):

Service Service Endpoint Private Endpoint Notes
Azure Storage (Blob/File/Queue/Table) Yes (Microsoft.Storage) Yes (per sub-resource) Richest model; both work
Azure SQL Database Yes (Microsoft.Sql) Yes (sqlServer) Proxy forced over PE
Azure SQL Managed Instance No (VNet-injected) n/a (already in VNet) Lives in your subnet natively
Azure Key Vault Yes (Microsoft.KeyVault) Yes (vault) PE is the production norm
Azure Cosmos DB Yes Yes (Sql/MongoDB/…) Per-API sub-resource
Azure Service Bus / Event Hubs Yes (Microsoft.ServiceBus) Yes (namespace) Premium tier for PE
Azure App Service / Functions No Yes (sites) Private Endpoint-only for inbound
Azure Container Registry No Yes (registry) Private Endpoint-only; Premium SKU
Azure Synapse Analytics Yes (Microsoft.Sql) Yes (multiple) SQL + Dev + Dedicated faces
Azure Cognitive / AI Services No Yes (account) Private Endpoint-only
Azure Monitor / Log Analytics No Yes (AMPLS) Via Azure Monitor Private Link Scope
Azure Cache for Redis No Yes PE on Premium; or VNet-injected
Azure Database for PostgreSQL/MySQL Yes (flexible: VNet-injected) Yes (single server) Flexible server injects into VNet
Azure Web PubSub / SignalR No Yes Private Endpoint-only
Azure Data Factory No Yes (multiple) Managed VNet + PE for sources

The takeaway from this table: a meaningful set of services — App Service inbound, Container Registry, Cognitive/AI Services, SignalR — are Private Endpoint-only, so if your estate includes them, you’re learning Private Endpoint regardless of cost preferences.

Private Endpoint, option by option

A Private Endpoint is the heavier, stronger tool: a NIC with a private IP in your subnet that maps to one sub-resource of one PaaS instance via Private Link. It is billed per hour and per gigabyte, it requires you to solve DNS, and it lets you disable the public endpoint. Stand it up in three logical pieces: the endpoint (NIC), the DNS zone + link, and the zone group (the A record), then disable public access on the service.

Creating a Private Endpoint for Blob storage

RG=rg-net; VNET=vnet-app; SUBNET=snet-pe; LOC=centralindia
SA_ID=$(az storage account show -n mystorageacct -g rg-data --query id -o tsv)

# 1) The Private Endpoint NIC, targeting the 'blob' sub-resource (groupId)
az network private-endpoint create \
  --name pe-mystorage-blob -g $RG -l $LOC \
  --vnet-name $VNET --subnet $SUBNET \
  --private-connection-resource-id "$SA_ID" \
  --group-id blob \
  --connection-name pe-mystorage-blob-conn

# 2) The Private DNS zone for blob, linked to the VNet
az network private-dns zone create -g $RG -n privatelink.blob.core.windows.net
az network private-dns link vnet create -g $RG \
  --zone-name privatelink.blob.core.windows.net \
  --name link-vnet-app --virtual-network $VNET --registration-enabled false

# 3) The zone group — auto-creates the A record (name → private IP)
az network private-endpoint dns-zone-group create -g $RG \
  --endpoint-name pe-mystorage-blob \
  --name default \
  --private-dns-zone privatelink.blob.core.windows.net \
  --zone-name privatelink-blob

# 4) Now turn OFF the public endpoint — only meaningful with a PE present
az storage account update -n mystorageacct -g rg-data --public-network-access Disabled
resource pe 'Microsoft.Network/privateEndpoints@2023-11-01' = {
  name: 'pe-mystorage-blob'
  location: location
  properties: {
    subnet: { id: peSubnet.id }
    privateLinkServiceConnections: [ {
      name: 'pe-mystorage-blob-conn'
      properties: {
        privateLinkServiceId: sa.id
        groupIds: [ 'blob' ]               // the sub-resource / face of the service
      }
    } ]
  }
}

resource dnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.blob.core.windows.net'
  location: 'global'
}

resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  parent: dnsZone
  name: 'link-vnet-app'
  location: 'global'
  properties: {
    virtualNetwork: { id: vnet.id }
    registrationEnabled: false
  }
}

resource zoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-11-01' = {
  parent: pe
  name: 'default'
  properties: {
    privateDnsZoneConfigs: [ {
      name: 'privatelink-blob'
      properties: { privateDnsZoneId: dnsZone.id }   // the zone group writes the A record here
    } ]
  }
}

The Private Endpoint option matrix

Every knob, option-by-option:

Setting / control What it does Default When to set it Trade-off / limit
groupIds (sub-resource) Which face of the service the PE serves required One per face you use One PE = one sub-resource
privateLinkServiceId The exact PaaS instance required Always Points at one resource
subnet Which subnet holds the NIC required A subnet with room Consumes a private IP
Static vs dynamic private IP Pin the NIC’s IP or let Azure pick dynamic Pin for firewall/DNS rules Manual IP management if static
Private DNS zone group Auto-writes the A record none Almost always Skip → you must add A records by hand
registrationEnabled on link Auto-register VM names in the zone false Leave false for privatelink zones true pollutes the zone
manualPrivateLinkServiceConnections Cross-tenant/-sub connection needing approval none Connecting to a resource you don’t own Stays Pending until approved
publicNetworkAccess on the service Disable the public endpoint Enabled After the PE works end-to-end Disabling before DNS works locks you out
privateEndpointNetworkPolicies on subnet Apply NSG/UDR to PE traffic historically Disabled Enable to filter PE traffic Older VNets bypassed NSGs
Application security group on the NIC Group PEs for NSG rules none Scale NSG rules cleanly Optional

Limits and quotas you will actually hit

Both mechanisms have real ceilings that bite at scale — a subnet that runs out of IPs mid-deploy, or a subscription approaching the Private Endpoint cap. The numbers that matter (defaults; some are raisable via support, confirm current values):

Limit Service Endpoint Private Endpoint Why it bites
Private IPs consumed 0 1 per endpoint PE subnet must have free IPs
Endpoints per VNet n/a ~1,000 (high) Rarely hit; plan subnets anyway
Endpoints per subscription per region n/a ~1,000 (raisable) Large estates approach it
Service Endpoints per subnet many n/a One per service family
VNet firewall rules per service ~200 (varies) n/a Capping subnet allow-rules
Private DNS zones per subscription n/a thousands Sprawl if not centralised
VNet links per private DNS zone n/a ~1,000 Hub zone linked to many spokes
A records per private DNS zone n/a ~25,000 One per endpoint name
Min subnet size for PEs (practical) n/a /27+ recommended /29 fills fast (5 usable)
Cross-region Service Endpoint global variant only each region its own PE SE is region-local by default

The sub-resource (groupId) reference

The groupId you pass must match the service’s sub-resource, and each maps to its own privatelink.* DNS zone. Getting either wrong is the second-most-common setup error. The ones you’ll use most:

Service groupId (sub-resource) privatelink.* DNS zone
Storage — Blob blob privatelink.blob.core.windows.net
Storage — File file privatelink.file.core.windows.net
Storage — Queue queue privatelink.queue.core.windows.net
Storage — Table table privatelink.table.core.windows.net
Storage — Data Lake Gen2 dfs privatelink.dfs.core.windows.net
Storage — Static website web privatelink.web.core.windows.net
SQL Database / Synapse sqlServer privatelink.database.windows.net
Key Vault vault privatelink.vaultcore.azure.net
Cosmos DB (SQL API) Sql privatelink.documents.azure.com
Service Bus / Event Hubs namespace privatelink.servicebus.windows.net
App Service / Functions sites privatelink.azurewebsites.net
Azure Container Registry registry privatelink.azurecr.io
Azure Monitor (AMPLS) azuremonitor several (privatelink.monitor.azure.com, …)

Connection approval states

When you connect to a resource in another subscription or tenant, the connection is manual and the resource owner must approve it. The state machine:

State Meaning What you do
Pending Awaiting the resource owner’s approval Ask the owner to approve; no traffic flows yet
Approved Owner accepted; the link is live Wire DNS; test
Rejected Owner declined Recreate after resolving with the owner
Disconnected The target resource was deleted/unlinked Recreate the endpoint against a valid target
# Resource owner approves a pending cross-subscription connection
az network private-endpoint-connection approve \
  --resource-group rg-data --name <connection-name> \
  --resource-name mystorageacct --type Microsoft.Storage/storageAccounts \
  --description "Approved for partner VNet"

DNS: the part everyone gets wrong

Ninety percent of “my Private Endpoint isn’t working” tickets are DNS. The NIC is fine; the name still resolves to the public IP, so traffic either takes the public path (works, but not private — the silent failure) or is blocked by publicNetworkAccess: Disabled (a hard failure that looks like the endpoint is broken). Understanding the CNAME chain makes every fix obvious.

When you create a Private Endpoint, Azure changes the public DNS for the service so its name becomes a CNAME to a privatelink name. Resolution then depends on whether your resolver can see the private zone:

Resolver context mystorage.blob.core.windows.net resolves to Private?
Azure VM in a VNet linked to the privatelink.blob… zone The private IP (via the zone’s A record) Yes
Azure VM in a VNet not linked to the zone The public IP (CNAME falls through) No
On-prem with a conditional forwarder to Azure DNS (168.63.129.16) via a DNS resolver/forwarder VM The private IP Yes
On-prem with no forwarder The public IP No
Anywhere, public access Disabled, resolving public IP Public IP that now refuses the connection Broken-looking

The CNAME chain, made concrete:

mystorage.blob.core.windows.net
   └─ CNAME → mystorage.privatelink.blob.core.windows.net
                 └─ A (in your Private DNS zone) → 10.10.2.4   (the PE's private IP)

If the VNet is linked to the privatelink.blob.core.windows.net zone, the A record wins and you get 10.10.2.4. If not, there is no A record to find and you fall through to the public CNAME target. The private DNS zone group on the endpoint is what writes that A record for you — which is why it is essentially mandatory.

The DNS decision table — pick your pattern:

If your clients are… Use this DNS pattern Why
Only Azure VMs in one VNet Private DNS zone linked to that VNet, zone group on the PE Simplest; auto A records
Azure VMs across many spokes Central privatelink zones in the hub, linked to all spokes (hub-and-spoke) One source of truth; see Private Link at scale
On-prem + Azure DNS Private Resolver (or a forwarder VM) in the hub; on-prem conditional-forwards to it On-prem can’t query Azure-internal DNS directly
Multi-region Regional PEs, but the privatelink zone is global — one zone, region-specific A records Zones aren’t regional; IPs are
Using a custom/3rd-party DNS Forward privatelink.* to Azure DNS (168.63.129.16) Your DNS must delegate the private zone

Diagnosing DNS in one command, from inside the VNet:

# From an Azure VM in the VNet — must return the PRIVATE IP (e.g. 10.x), not a public one
nslookup mystorageacct.blob.core.windows.net
# Or, more telling, the full chain:
dig mystorageacct.blob.core.windows.net +noall +answer
# Expect: CNAME to ...privatelink.blob... then an A record in your 10.x space

The registration-enabled flag on the VNet link must be false for privatelink.* zones. Setting it true turns on auto-registration of VM hostnames into that zone, which both pollutes it and can collide — it’s for VM name resolution zones, never for privatelink zones.

NSG, UDR and how each endpoint interacts with routing

Both mechanisms interact with Network Security Groups and route tables (UDRs) — and the interactions are non-obvious enough to cause real outages, especially the “route everything through the firewall” pattern.

For Service Endpoints, Azure adds a system route for the service’s prefixes pointing at VirtualNetworkServiceEndpoint. If you also have a UDR sending 0.0.0.0/0 to a firewall/NVA, the more specific service-endpoint route still wins for that service’s traffic — which is usually what you want (PaaS traffic stays on the optimised backbone, not hairpinned through the firewall). NSGs apply normally; you can use the service tag (e.g. Storage, Sql, AzureKeyVault) in NSG rules instead of IP ranges.

For Private Endpoints, the NIC has a /32 route in the VNet so traffic to the private IP routes directly. Historically, traffic to a Private Endpoint bypassed NSGs and UDRs on the source subnet — a frequent surprise for teams who thought an NSG rule was filtering it. Azure now supports network policies on Private Endpoints (privateEndpointNetworkPolicies = Enabled on the subnet) so NSGs and UDRs do apply; on older VNets this defaults off and you must enable it explicitly.

The interaction grid:

Scenario Service Endpoint Private Endpoint
System route added Service prefixes → VirtualNetworkServiceEndpoint /32 to the NIC’s private IP
0.0.0.0/0 → firewall UDR SE route is more specific → bypasses firewall /32 more specific → bypasses firewall (unless policies on)
NSG filtering of the traffic Applies; use service tags (Storage, Sql) Bypassed by default; enable PE network policies to filter
Force-tunnel to on-prem SE traffic still backbone (route wins) PE traffic to private IP stays in VNet
Service tag in NSG Storage, Sql, AzureKeyVault, etc. Not applicable (it’s a private IP, not a tag range)
UDR to inspect PaaS egress SE bypasses inspection (by design) Enable PE network policies + UDR to inspect

A NSG example using a service tag with a Service Endpoint (allow the subnet to reach Storage, deny the rest of the internet):

az network nsg rule create -g rg-net --nsg-name nsg-workload -n allow-storage \
  --priority 200 --direction Outbound --access Allow --protocol Tcp \
  --destination-address-prefixes Storage --destination-port-ranges 443
az network nsg rule create -g rg-net --nsg-name nsg-workload -n deny-internet \
  --priority 4000 --direction Outbound --access Deny --protocol '*' \
  --destination-address-prefixes Internet --destination-port-ranges '*'

When you actually need PE network policies on, and the trade-off:

Network policy on PE subnet Effect When to enable
Disabled (legacy default) NSGs/UDRs ignored for PE traffic Simplicity; you trust the subnet
Enabled (NSG) NSG rules apply to PE traffic You must filter who reaches the PE
Enabled (Route Table) UDRs apply to PE traffic You must inspect/redirect PE egress
Both enabled Full NSG + UDR enforcement Zero-trust subnets, regulated egress

Per-service mechanics: Storage, SQL and Key Vault

The two mechanisms behave subtly differently across services. These three cover most real designs; the patterns generalise.

Storage — the richest sub-resource model

Storage is where the groupId model bites hardest: one account, up to six faces (blob, file, queue, table, dfs, web), each a separate Private Endpoint and separate DNS zone if you use them. A common mistake is creating a blob endpoint and wondering why File shares still fail — they need their own. Storage supports both Service Endpoints (cheap, firewall-the-subnet) and Private Endpoints (private IP, disable public).

Storage facet Service Endpoint Private Endpoint
Granularity Whole account, via subnet firewall rule Per sub-resource (blob/file/queue/…)
DNS change None One privatelink.<svc>.core.windows.net zone per face
Disable public access No (selected networks) Yes (--public-network-access Disabled)
On-prem reach No Yes
Cost Free Per endpoint, per face
Exfiltration control Service Endpoint Policy (account allow-list) Inherent (points at one account) + Private Link policy
Typical use Dev/test, cost-sensitive, public-OK Regulated data, private-only mandate

SQL Database — and the Proxy vs Redirect trap

SQL Database supports both. The Private Endpoint targets the sqlServer sub-resource and uses the privatelink.database.windows.net zone. The subtle trap is the connection policy: SQL offers Proxy (all traffic via the gateway on 1433) and Redirect (the client is redirected to the database node on ports 11000–11999). Inside Azure, Redirect is faster — but when you go through a Private Endpoint, the platform forces Proxy-style behaviour over 1433, and if your NSG only opened 1433 you’re fine, whereas if you relied on Redirect’s port range from an on-prem client over a Private Endpoint, behaviour differs. Know which policy is in effect.

# Check / set the SQL server connection policy
az sql server conn-policy show -g rg-data -s mysqlserver
az sql server conn-policy update -g rg-data -s mysqlserver --connection-type Proxy
# Private Endpoint for the SQL logical server
az network private-endpoint create -n pe-sql -g rg-net -l centralindia \
  --vnet-name vnet-app --subnet snet-pe \
  --private-connection-resource-id $(az sql server show -g rg-data -n mysqlserver --query id -o tsv) \
  --group-id sqlServer --connection-name pe-sql-conn
SQL connectivity aspect Service Endpoint Private Endpoint
Sub-resource / zone n/a sqlServer / privatelink.database.windows.net
Ports 1433 1433 (Proxy is forced)
Connection policy interplay Redirect (11000–11999) possible Effectively Proxy over the PE
Disable public access No Yes (--public-network-access Disabled on server)
On-prem reach No Yes
Failover-group / read-replica Works; firewall each Each server/listener needs its own PE

Key Vault — secrets over a private IP

Key Vault is a top Private Endpoint target because secrets and keys are the crown jewels. It targets the vault sub-resource and the privatelink.vaultcore.azure.net zone. Key Vault also has a “Allow trusted Microsoft services” toggle and a firewall; with a Private Endpoint you typically set publicNetworkAccess = Disabled and rely on the private IP plus a managed identity. A frequent failure: an App Service tries to read a Key Vault reference at boot, the vault’s public access is disabled, the app’s VNet integration routes outbound through the VNet, but the privatelink.vaultcore.azure.net zone isn’t linked to that VNet, so the name resolves public, hits the disabled endpoint, and the app crash-loops with an empty secret.

az network private-endpoint create -n pe-kv -g rg-net -l centralindia \
  --vnet-name vnet-app --subnet snet-pe \
  --private-connection-resource-id $(az keyvault show -n kv-prod --query id -o tsv) \
  --group-id vault --connection-name pe-kv-conn
az keyvault update -n kv-prod --public-network-access Disabled
Key Vault aspect Service Endpoint Private Endpoint
Sub-resource / zone n/a vault / privatelink.vaultcore.azure.net
Disable public access No (selected networks) Yes
“Trusted Microsoft services” Still relevant Often combined with PE for platform access
App Service KV references Need VNet route + SE firewall rule Need VNet route + zone linked to that VNet
On-prem secret reads No Yes (with DNS forwarding)
Typical posture Lower-sensitivity vaults Production secrets/keys, compliance

Ports, protocols and service tags per service

When you write NSG rules — service tags for the Service-Endpoint path, or /32 allows on the Private-Endpoint path — you need the exact port and the right service tag. The reference you’ll open while authoring NSG rules:

Service Port(s) Protocol NSG service tag (for SE) Private Endpoint NIC port
Storage (Blob/File/Queue/Table) 443 HTTPS Storage (or Storage.<region>) 443 to the private IP
Azure Files (SMB) 445 SMB/TCP Storage 445 to the private IP
SQL Database (Proxy) 1433 TDS/TLS Sql (or Sql.<region>) 1433
SQL Database (Redirect) 11000–11999 TDS/TLS Sql n/a (PE forces Proxy)
Key Vault 443 HTTPS AzureKeyVault 443
Cosmos DB 443 HTTPS AzureCosmosDB 443
Service Bus / Event Hubs 5671/5672, 443 AMQP / HTTPS ServiceBus / EventHub 5671/443
App Service (inbound) 443/80 HTTPS/HTTP n/a (PE-only) 443
Container Registry 443 HTTPS n/a (PE-only) 443
Azure Monitor (AMPLS) 443 HTTPS AzureMonitor 443

Two notes that save NSG debugging time: the regional service tag (e.g. Storage.centralindia) is tighter than the global one and is preferred when you only reach in-region PaaS; and on the Private Endpoint path you do not use service tags at all — the target is a /32 private IP, so you write a host route or an AllowVnetInBound-style rule, which is exactly why people who try to use the Storage tag to filter PE traffic find it has no effect.

Data-exfiltration prevention: the security angle that decides it

For many regulated teams the deciding factor isn’t “private routing is nice,” it’s stopping data exfiltration — a compromised workload or malicious insider copying data to storage they control. This is where the two mechanisms diverge most sharply, and where Private Endpoint plus policy is the strong answer.

A plain Service Endpoint routes the subnet to the whole Storage service on the backbone — so a process in that subnet can still write to any storage account in the world, including an attacker’s. The Service Endpoint did not reduce exfiltration risk; it only pinned the route. To close it you must add a Service Endpoint Policy restricting which accounts the subnet may reach. A Private Endpoint is inherently narrower — it maps to one specific account — and combined with publicNetworkAccess = Disabled on your accounts plus blocking the public Storage endpoint at the firewall, the subnet has no path to foreign accounts at all.

The exfiltration posture, compared:

Control Stops reaching foreign accounts? How Effort
Service Endpoint alone No Routes to whole service Low
Service Endpoint + SE Policy Yes (allow-list) Subnet may reach only listed accounts Medium
Private Endpoint alone Partially Private IP to your account; public Storage still routable unless blocked Medium
Private Endpoint + disable public + firewall deny public Storage Yes No route to any public Storage endpoint Higher
Azure Firewall + FQDN rules Yes Allow-list FQDNs of your accounts only Higher

A defence-in-depth recipe combines them: Private Endpoints for your own PaaS, publicNetworkAccess: Disabled on those resources, WEBSITE_VNET_ROUTE_ALL=1 (or equivalent) to force workload egress through the VNet, and an Azure Firewall with FQDN rules denying *.blob.core.windows.net except your own accounts. Now a compromised process can neither resolve nor route to anyone else’s storage.

Choosing between them: the decision matrix

With the mechanics covered, the choice collapses to a short set of yes/no questions. Run your requirement down the left column and the answer is in the right:

If your requirement is… It points to… Because
“No public endpoint on this service” Private Endpoint Only PE allows publicNetworkAccess: Disabled
“Reach this PaaS from on-premises privately” Private Endpoint SE is VNet-local; PE’s private IP routes over ER/VPN
“Stop the subnet exfiltrating to foreign accounts” PE (or SE + SE Policy) PE targets one resource; SE alone routes to all
“Cheapest possible, public exposure is fine” Service Endpoint Free; no DNS work
“Pin routing to the backbone, keep it simple” Service Endpoint Route + identity, nothing to host
“Service is Private-Endpoint-only (ACR, AI, SignalR)” Private Endpoint No SE support exists
“I can’t manage Private DNS right now” Service Endpoint PE without DNS is the #1 failure
“Regulated data (PHI/PCI/gov)” Private Endpoint Auditors require the public surface gone
“Dev/test, low-sensitivity, internal-only” Service Endpoint Zero cost, adequate isolation
“Per-resource granularity, not per-subnet” Private Endpoint PE maps to one instance/sub-resource
“Many subnets, one PaaS, coarse allow is fine” Service Endpoint Add each subnet to the firewall
“Hybrid DNS already exists (resolver in hub)” Private Endpoint The hard part (DNS) is already solved

And the same logic as a per-service default recommendation, so a landing-zone standard can cite a row:

Service Recommended default Acceptable alternative Rationale
Storage (prod data) Private Endpoint per face SE + SE Policy (low-sensitivity) Disable public; bound exfiltration
SQL Database (prod) Private Endpoint SE (internal dev DBs) Remove public surface for data
Key Vault (prod secrets) Private Endpoint SE (low-sensitivity vaults) Crown jewels; private-only
App Service (inbound) Private Endpoint PE-only for private inbound
Container Registry Private Endpoint PE-only; secure supply chain
Cosmos DB (prod) Private Endpoint SE Per-API private IP
Service Bus (prod) Private Endpoint (Premium) SE (Standard) Messaging on backbone
Azure Monitor AMPLS (Private Link Scope) Public + firewall Private log ingestion
Dev/test any PaaS Service Endpoint Public + IP firewall Cost-first; public-OK

Architecture at a glance

The diagram puts the two mechanisms on one canvas so you can see, literally, where the difference lives. Read it left to right as a request leaving a workload subnet. On the top path (Service Endpoint), traffic from the subnet picks up the injected backbone route and the subnet’s identity, crosses the Azure backbone, and arrives at the storage account’s still-public IP — admitted only because the account’s firewall has a rule allowing that subnet. The public endpoint is narrowed but very much alive: anyone the firewall allows can still reach it from the public internet, and the FQDN still resolves to that public IP. On the bottom path (Private Endpoint), the workload resolves the same FQDN, but because the VNet is linked to the privatelink DNS zone the name returns a private IP on a NIC inside the subnet; Private Link proxies that private IP to the exact storage account over the backbone, and the account’s public access is Disabled, so the public IP no longer answers anyone.

Follow the numbered badges. (1) marks the most common failure on the private path: DNS still resolving to the public IP because the privatelink zone isn’t linked or the zone group never wrote the A record — the connection silently takes the public route or, with public access disabled, fails outright. (2) is the Service Endpoint’s structural limit: the public IP persists, so an audit finding and exfiltration risk remain unless a Service Endpoint Policy is added. (3) is the sub-resource (groupId) mismatch — a blob endpoint can’t serve file traffic. (4) is the on-prem reach difference: Service Endpoints are VNet-local, so a branch over VPN/ExpressRoute must use the Private Endpoint with DNS forwarding. The legend narrates each as symptom, the one command to confirm it, and the fix.

Two paths from a workload subnet to the same Azure Storage account, contrasting Service Endpoint and Private Endpoint. Top path: the subnet has a Microsoft.Storage service endpoint that adds a backbone route and subnet identity; traffic crosses the Azure backbone to the storage account's still-public IP, admitted by a firewall rule allowing the subnet, with the public endpoint remaining alive and the FQDN resolving public. Bottom path: the workload resolves the FQDN to a private IP because the VNet is linked to the privatelink.blob.core.windows.net Private DNS zone; the private IP lives on a network interface inside the subnet and Private Link proxies it to the exact storage account over the backbone, with the account's public network access disabled. Numbered badges mark the four decisive differences and failure points: (1) DNS still resolving to the public IP, the silent Private Endpoint failure; (2) the Service Endpoint leaving a public IP and audit/exfiltration exposure; (3) a sub-resource groupId mismatch where a blob endpoint cannot serve file traffic; (4) on-prem reachability, where Service Endpoints are VNet-local and only the Private Endpoint serves branches over VPN or ExpressRoute. A legend narrates each badge as symptom, confirm command, and fix.

Real-world scenario

Medora Health runs a patient-portal platform on Azure in Central India: an App Service web tier, an Azure SQL Database with eight years of clinical records, Blob storage for scanned documents and DICOM images, and an Azure Key Vault holding the database connection string and a document-signing key. The platform team is six engineers; the workload spans two spoke VNets behind a hub. Their security baseline, driven by a healthcare-data regulation, mandated a single uncompromising rule: no patient-data service may present a public endpoint, and no data may be exfiltratable to storage the company does not own. The monthly Azure spend was about ₹3.1 lakh.

The original design used Service Endpoints everywhere — Microsoft.Sql, Microsoft.Storage, Microsoft.KeyVault on the workload subnets, with each service’s firewall set to “selected networks.” It passed a casual review because the firewalls denied the open internet. It failed the formal audit on three findings the team hadn’t appreciated. First, every service still answered on a public IP — the auditor demonstrated reaching the SQL gateway FQDN from a laptop on the public internet (it refused auth, but the surface was the finding). Second, a penetration tester showed that a process on the workload subnet could write to a storage account in a personal MSDN subscription — the Service Endpoint routed the subnet to all of Storage, so exfiltration was wide open. Third, the branch clinics connecting over ExpressRoute couldn’t use the Service Endpoints at all (VNet-local), so they’d been whitelisting public IP ranges on the firewalls — exactly the public exposure the baseline forbade.

The remediation was a wholesale move to Private Endpoints, and the migration is the lesson. They created a Private Endpoint per service per region: sqlServer for the database, blob and file for the storage account (two endpoints — a junior engineer initially made only blob and spent an afternoon on why File shares 403’d), and vault for Key Vault. They hosted the privatelink.database.windows.net, privatelink.blob.core.windows.net, privatelink.file.core.windows.net and privatelink.vaultcore.azure.net zones centrally in the hub, linked to both spokes — one source of truth. They stood up an Azure DNS Private Resolver in the hub and pointed on-prem conditional forwarders at it, so the ExpressRoute clinics finally resolved the private IPs instead of needing public IP whitelists. Then, only after nslookup from each spoke and from a clinic returned a 10.x address, they set publicNetworkAccess = Disabled on SQL, Storage and Key Vault.

Two things went wrong during cutover, both DNS. First, the App Service tier — which had VNet integration but its spoke was not yet linked to the privatelink.vaultcore.azure.net zone — crash-looped on boot the moment Key Vault’s public access was disabled, because the Key Vault reference resolved to the now-dead public IP. The fix was linking the zone to the integration VNet; the lesson was link DNS before disabling public, and verify resolution from the actual client. Second, a forgotten Service Endpoint Policy had still been the only exfiltration guard on a legacy subnet; once Private Endpoints replaced the Service Endpoints, the policy was moot, but they kept an Azure Firewall FQDN rule denying *.blob.core.windows.net except their own accounts as belt-and-suspenders. The re-audit passed clean: zero public endpoints on patient data, no route to foreign storage, branches private over ExpressRoute. Cost rose by the Private Endpoint hourly + data charges — about ₹6,400/month for the dozen endpoints and the resolver — which the compliance sign-off made trivially worth it.

The migration as a before/after, because the contrast is the teaching:

Concern Before (Service Endpoints) After (Private Endpoints)
SQL public surface Public IP, “selected networks” Disabled; private IP only
Storage exfiltration Subnet could reach any account PE to one account + firewall FQDN deny
Branch (ExpressRoute) access Public IP whitelist (forbidden) Private IP via DNS Private Resolver
Key Vault Public, firewalled Private IP, public disabled
DNS Untouched (public) Central privatelink zones, linked to spokes
Audit result 3 findings Clean
Extra monthly cost ₹0 ~₹6,400

Advantages and disadvantages

Neither mechanism is “better” in the abstract — they sit at different points on a cost/security/complexity curve. Weigh them honestly:

Service Endpoint Private Endpoint
Advantages Free; trivial to enable (one subnet flag); no DNS work; pins route to backbone; uses service tags in NSGs; good enough when public exposure is acceptable True private IP in your VNet; public endpoint can be disabled; reachable from on-prem; strong exfiltration posture (one resource); per-instance granularity; the only path to “no public endpoints”
Disadvantages Service keeps a public IP (audit finding persists); can’t disable public access; VNet-local (no on-prem); weak exfiltration control without SE Policy; coarse (subnet-level); not supported by every service Costs per hour + per GB; you must solve DNS (the #1 failure); one endpoint per sub-resource (Storage gets pricey); consumes private IPs; cross-tenant needs approval; historically bypasses NSGs

When each matters: reach for a Service Endpoint when the workload is internal-only, public exposure of the PaaS is acceptable to your security baseline, you want to pin routing and firewall the service to a subnet at zero cost, and you have no on-prem clients — dev/test environments and lower-sensitivity data are the sweet spot. Reach for a Private Endpoint the moment any of these is true: the data is regulated or sensitive, your baseline says “no public endpoints,” you must reach the PaaS from on-premises, or exfiltration prevention is a hard requirement. In a modern enterprise landing zone, Private Endpoint is the default and Service Endpoint is the documented exception. The decisive practical difference is that “we disabled public access” is a sentence you can only say truthfully with a Private Endpoint.

Hands-on lab

Stand up a Private Endpoint for a real storage account, prove DNS resolves to the private IP, disable public access, and tear it all down — free-tier-friendly except the small Private Endpoint hourly charge (delete at the end; cost is a few rupees). Run in Cloud Shell (Bash).

Step 1 — Variables and resource group.

RG=rg-pe-lab; LOC=centralindia
VNET=vnet-pe-lab; SNET_VM=snet-vm; SNET_PE=snet-pe
SA=stpelab$RANDOM           # globally-unique
az group create -n $RG -l $LOC -o table

Step 2 — VNet with two subnets (one for a test VM, one for the endpoint).

az network vnet create -g $RG -n $VNET --address-prefix 10.20.0.0/16 \
  --subnet-name $SNET_VM --subnet-prefix 10.20.1.0/24 -o table
az network vnet subnet create -g $RG --vnet-name $VNET -n $SNET_PE \
  --address-prefix 10.20.2.0/24 -o table

Step 3 — A storage account (public for now) and a tiny test VM.

az storage account create -n $SA -g $RG -l $LOC --sku Standard_LRS --kind StorageV2 -o table
az vm create -g $RG -n vm-test --image Ubuntu2204 --size Standard_B1s \
  --vnet-name $VNET --subnet $SNET_VM --admin-username azureuser \
  --generate-ssh-keys --public-ip-address "" -o table

Step 4 — Confirm it currently resolves PUBLIC (the “before”).

az vm run-command invoke -g $RG -n vm-test --command-id RunShellCommand \
  --scripts "nslookup ${SA}.blob.core.windows.net"
# Expect a PUBLIC IP (not 10.x) — no Private Endpoint yet.

Step 5 — Create the Private Endpoint for the blob sub-resource.

SA_ID=$(az storage account show -n $SA -g $RG --query id -o tsv)
az network private-endpoint create -n pe-blob -g $RG -l $LOC \
  --vnet-name $VNET --subnet $SNET_PE \
  --private-connection-resource-id "$SA_ID" --group-id blob \
  --connection-name pe-blob-conn -o table

Step 6 — Private DNS zone, VNet link, and the zone group (writes the A record).

az network private-dns zone create -g $RG -n privatelink.blob.core.windows.net -o table
az network private-dns link vnet create -g $RG \
  --zone-name privatelink.blob.core.windows.net -n link-lab \
  --virtual-network $VNET --registration-enabled false -o table
az network private-endpoint dns-zone-group create -g $RG \
  --endpoint-name pe-blob -n default \
  --private-dns-zone privatelink.blob.core.windows.net --zone-name blob -o table

Step 7 — Confirm it now resolves PRIVATE (the “after”).

az vm run-command invoke -g $RG -n vm-test --command-id RunShellCommand \
  --scripts "nslookup ${SA}.blob.core.windows.net"
# Expect a 10.20.2.x address — the Private Endpoint NIC. THIS is the proof it's private.

Step 8 — Disable the public endpoint (now safe, because DNS is private).

az storage account update -n $SA -g $RG --public-network-access Disabled -o table
# From the VM (private path) it still works; from the public internet it now refuses.

Step 9 — Teardown.

az group delete -n $RG --yes --no-wait

Expected results table:

Step Command Expected outcome
4 nslookup before PE A public IP
5 create PE NIC created in snet-pe
6 zone + link + group A record written in the private zone
7 nslookup after PE A 10.20.2.x private IP
8 disable public Private path works; public refuses
9 group delete All resources removed

Common mistakes & troubleshooting

This is the differentiator. Every failure below is one we’ve actually debugged. Match the symptom, run the confirm, apply the fix. The master playbook first, then detail on the worst offenders.

# Symptom Root cause Confirm (exact command / portal path) Fix
1 App connects but traffic isn’t private privatelink zone not linked / no A record — DNS resolves public nslookup <fqdn> from a VNet VM returns a public IP Link the privatelink.* zone to the VNet; add zone group to the PE
2 App hard-fails after disabling public access Public disabled before DNS was private; name → dead public IP nslookup returns public IP; service publicNetworkAccess: Disabled Re-enable public, fix DNS, verify 10.x, then disable again
3 File shares 403 though Blob works Only a blob PE exists; file needs its own az network private-endpoint list shows only groupId: blob Create a file Private Endpoint + privatelink.file… zone
4 On-prem clients still resolve public No conditional forwarder to Azure DNS nslookup from on-prem returns public IP Deploy DNS Private Resolver/forwarder; conditional-forward privatelink.*
5 403 from the service despite a Service Endpoint defaultAction still Allow, or wrong subnet in rule Service Networking blade; networkAcls.defaultAction Set Deny; add the correct subnet’s resource ID
6 Service Endpoint “doesn’t restrict anything” SE pins route but doesn’t disable public; firewall left open Service still answers on public IP from the internet Use Private Endpoint, or tighten firewall + SE Policy
7 PE connection stuck Pending Cross-sub/-tenant manual connection awaiting approval PE → Connection state = Pending Resource owner runs az network private-endpoint-connection approve
8 NSG rule “isn’t filtering” PE traffic PE traffic bypasses NSG by default Subnet privateEndpointNetworkPolicies = Disabled Set it Enabled to apply NSG/UDR to PE traffic
9 SQL connects publicly but not via PE DNS not private, or relying on Redirect ports nslookup mysql.database.windows.net; conn-policy Wire privatelink.database… zone; expect Proxy over 1433
10 Subnet can write to a foreign storage account Service Endpoint routes to all of Storage Pen-test writes to an external account succeed Add a Service Endpoint Policy, or move to Private Endpoint
11 “Subnet doesn’t support Service Endpoints” Subnet delegated/locked, or service unsupported Subnet shows a delegation; SE add errors Use an undelegated subnet; confirm the service supports SE
12 Private Endpoint create fails — no IPs The PE subnet is too small / full Subnet free-IP count is 0 Use a larger PE subnet (plan one IP per endpoint)
13 App Service KV reference empty at boot Integration VNet not linked to vaultcore zone Environment variables blade shows resolve error Link privatelink.vaultcore.azure.net to the integration VNet
14 Multi-region: zone “already exists” error privatelink zones are global, not per-region Trying to create the zone twice Reuse one global zone; add region-specific A records

Mistake 1 & 2 — DNS still resolves public (the silent and the loud failure)

These are the same root cause with opposite symptoms. If the FQDN resolves to the public IP and public access is enabled, the connection works but isn’t private (silent — passes functional tests, fails audits). If you then disable public access without fixing DNS, the name resolves to a now-dead public IP and the app hard-fails (loud — looks like the endpoint broke). The fix order is the lesson: wire DNS, verify 10.x from the actual client, then disable public.

# The single most useful check — run it from an actual client VM in the VNet:
az vm run-command invoke -g rg -n vm-test --command-id RunShellCommand \
  --scripts "getent hosts mystorageacct.blob.core.windows.net"
# Must show a private (10.x / your space) address. A public IP here = DNS not private.

Confirm the zone is linked and the zone group exists:

az network private-dns link vnet list -g rg --zone-name privatelink.blob.core.windows.net -o table
az network private-endpoint dns-zone-group list -g rg --endpoint-name pe-mystorage-blob -o table

Mistake 3 — one sub-resource per face

A blob endpoint serves Blob only. Queue, Table, File, DFS and Web each need their own Private Endpoint and their own privatelink.* zone. List what you actually have:

az network private-endpoint list -g rg \
  --query "[].{name:name, group:privateLinkServiceConnections[0].groupIds[0]}" -o table

The faces and their independent requirements:

You use… Need groupId Need zone
Blob containers blob privatelink.blob.core.windows.net
Azure Files shares file privatelink.file.core.windows.net
Storage queues queue privatelink.queue.core.windows.net
Table storage table privatelink.table.core.windows.net
Data Lake Gen2 dfs privatelink.dfs.core.windows.net

Mistake 4 — on-prem resolves public

A Private Endpoint’s private IP is reachable from on-prem over VPN/ExpressRoute, but on-prem DNS doesn’t know the private zone. You need a resolver in Azure that on-prem conditionally forwards to. Use Azure DNS Private Resolver (or a forwarder VM at 168.63.129.16).

# On-prem DNS server: conditional-forward privatelink.* to the Azure resolver inbound IP
# (Pseudo — configured on your on-prem DNS, pointing at the resolver's inbound endpoint IP)
#   forward zone "privatelink.blob.core.windows.net"  ->  <resolver-inbound-IP>

Mistake 8 — NSG not filtering Private Endpoint traffic

On older VNets, PE traffic ignores NSGs and UDRs. To filter it, enable network policies on the subnet:

az network vnet subnet update -g rg --vnet-name vnet-app -n snet-pe \
  --disable-private-endpoint-network-policies false   # i.e. ENABLE the policies

The decision table for the trickiest calls:

If you see… It’s probably… Do this
Works but audit says “public” DNS resolving public, or SE-only design Add PE + Private DNS; disable public
Hard 403/connection-refused after a “private” change Public disabled before DNS fixed Re-enable, fix DNS, re-disable
Blob fine, File broken Missing per-face PE Add the file endpoint + zone
On-prem can’t reach private PaaS No DNS forwarding DNS Private Resolver + conditional forward
NSG rule ignored on the PE subnet PE network policies off Enable PE network policies
Subnet reaches a foreign account Plain Service Endpoint SE Policy or Private Endpoint
PE stuck not passing traffic Connection still Pending Owner approves the connection

Best practices

Production-grade rules, learned the hard way:

Security notes

The security posture is the entire reason this choice exists. Hold these as invariants:

Control Service Endpoint Private Endpoint Note
Reduce public attack surface No (public IP remains) Yes (Disabled) Only PE removes the surface
Encryption in transit TLS (unchanged) TLS (unchanged) Neither adds encryption; both assume HTTPS
Network isolation Route + firewall identity True private IP in your VNet PE is genuine isolation
Least privilege (identity) Pair with managed identity + RBAC Pair with managed identity + RBAC Network ≠ auth; do both
Data-exfiltration prevention Weak (needs SE Policy) Strong (one resource + firewall) The decisive security difference
On-prem private reach No Yes Avoids public-IP whitelisting
Auditability “Selected networks” still public “No public endpoint” provable Auditors want the surface gone

Two non-negotiables. First, network controls are not authentication — a Private Endpoint does not replace RBAC, managed identities, or SAS hygiene; it complements them. An attacker inside your VNet still needs to be stopped by identity. Pair every private endpoint with least-privilege access (see Azure Key Vault: Secrets, Keys & Certificates). Second, “private” is only true once DNS is private — a Private Endpoint with the FQDN still resolving public is a false sense of security that passes functional tests; verify resolution from the real client before claiming isolation.

Cost & sizing

Service Endpoints are free — there is no charge for enabling them or for the traffic. Private Endpoints are billed two ways: an hourly charge per endpoint (roughly $0.01/hour ≈ ₹0.85/hour, about ₹600/month per endpoint) plus a per-GB data-processing charge (inbound and outbound, roughly $0.01/GB ≈ ₹0.85/GB). The cost driver is therefore the number of endpoints (one per service per face per region) and the data volume through them. Private DNS zones are essentially free (a tiny per-zone charge plus query volume).

The cost model at a glance (figures approximate, India regions, mid-2026 — always confirm current pricing):

Item Service Endpoint Private Endpoint
Enable / hourly Free ~₹0.85/hr (~₹600/mo) per endpoint
Data processed Free ~₹0.85/GB (in + out)
Private DNS zone n/a ~₹40/mo per zone + tiny query cost
Per Storage account using blob+file+queue Free 3 endpoints (~₹1,800/mo) + data
Cross-region Free (global SE variant) One endpoint per region

Right-sizing rules and what each saves:

Sizing decision Cheaper choice Saves When it’s safe
Use SE for non-sensitive PaaS Service Endpoint Full PE cost Public exposure acceptable
One endpoint per used face only Don’t create unused queue/table PEs ₹600/mo each You don’t use that face
Share one global privatelink zone across regions One zone, many A records Duplicate zone admin Multi-region
Centralise zones in the hub One linked zone set Per-spoke zone sprawl Hub-and-spoke
Keep data in-region Avoid cross-region PE data egress Per-GB + egress Co-locate workload + PaaS

A worked footprint: a workload using Blob + File + SQL + Key Vault privately needs 4 Private Endpoints (blob, file, sqlServer, vault) ≈ ₹2,400/month in hourly charges, plus DNS (~₹160/month for four zones), plus data processing scaling with traffic — call it ₹3,000–4,000/month for a moderate-traffic app. For a regulated workload that is a rounding error against the compliance value; for a dev environment with public-OK data, the same isolation costs ₹0 with Service Endpoints, which is exactly when to use them.

Interview & exam questions

Common in AZ-700 (Networking), AZ-500 (Security) and SC-100, and in senior cloud-architect interviews:

  1. What is the core difference between a Service Endpoint and a Private Endpoint? A Service Endpoint adds a backbone route and the subnet’s identity so the PaaS firewall can allow that subnet, but the service keeps its public IP. A Private Endpoint creates a NIC with a private IP in your subnet mapping to the PaaS instance via Private Link, letting you disable the public endpoint entirely.

  2. Can you disable a service’s public access with a Service Endpoint? No. A Service Endpoint only sets the firewall to “selected networks” — the public IP still exists and still answers allowed callers. Only a Private Endpoint lets you set publicNetworkAccess = Disabled.

  3. What is the single most common Private Endpoint failure? DNS still resolving to the public IP — the privatelink zone isn’t linked to the VNet or the zone group never wrote the A record. The connection works over the public path (silent) or fails if public access was disabled (loud).

  4. Why does a Service Endpoint not stop data exfiltration by itself? It routes the subnet to the entire service over the backbone, so the subnet can still reach any account of that service, including an attacker’s. You need a Service Endpoint Policy to bound it to an allow-list, or a Private Endpoint (which targets one resource).

  5. A storage account uses Blob and File; you created a Private Endpoint for blob and File still fails. Why? Each sub-resource (groupId) needs its own Private Endpoint and its own privatelink.* DNS zone — blob does not cover file. Create a file endpoint and zone.

  6. How do on-premises clients reach a Private Endpoint privately? Over VPN/ExpressRoute to the VNet, with on-prem DNS conditionally forwarding the privatelink.* zones to an Azure DNS Private Resolver (or forwarder VM) so the FQDN resolves to the private IP. Service Endpoints cannot serve on-prem at all.

  7. Why might an NSG rule appear not to filter Private Endpoint traffic? On older VNets, PE traffic bypassed NSGs and UDRs by default. Enable privateEndpointNetworkPolicies on the subnet to make NSG/UDR rules apply.

  8. What groupId and DNS zone does Azure SQL Database use, and what’s the connection-policy nuance? groupId sqlServer with privatelink.database.windows.net. Over a Private Endpoint, SQL effectively uses Proxy mode on 1433 rather than Redirect’s 11000–11999 port range.

  9. Are privatelink Private DNS zones regional? No — they are global. You create one zone (e.g. privatelink.blob.core.windows.net) and add region-specific A records for endpoints in different regions; you do not create the zone per region.

  10. When is a Service Endpoint the right choice over a Private Endpoint? When the workload is VNet-internal, public exposure of the PaaS is acceptable to your baseline, you want zero cost and no DNS work, and there are no on-prem clients — typically dev/test or low-sensitivity data.

  11. What does the private DNS zone group on an endpoint do, and why use it? It automatically creates and maintains the A record (name → private IP) in the linked private zone, so you never hand-maintain DNS records — the step that, omitted, breaks “private.”

  12. You disabled public access and the app immediately crash-looped. What happened and how do you prevent it? DNS still resolved to the public IP, which now refuses connections, so the app couldn’t reach the service. Always verify nslookup returns the private IP from the actual client before disabling public access.

Quick check

  1. True or false: a Service Endpoint gives the PaaS service a private IP in your VNet.
  2. Which mechanism lets you set publicNetworkAccess = Disabled and have it mean something?
  3. Your Private Endpoint is created but traffic isn’t private. What’s the first thing to check?
  4. A storage account needs private Blob and File access. How many Private Endpoints?
  5. Can a branch office over ExpressRoute use a Service Endpoint on an Azure subnet?

Answers

  1. False. A Service Endpoint adds a backbone route and subnet identity; the service keeps its public IP. Only a Private Endpoint places a private IP in your VNet.
  2. Private Endpoint. With a Service Endpoint the firewall narrows to “selected networks” but the public endpoint still answers; only a Private Endpoint lets you truly disable public access.
  3. DNS. Run nslookup <fqdn> from a VM in the VNet — it must return a private (10.x) IP. If it returns a public IP, the privatelink zone isn’t linked or the zone group is missing.
  4. Two — one for the blob sub-resource and one for file, each with its own privatelink.* DNS zone.
  5. No. Service Endpoints are VNet-local — they only affect traffic originating in the subnet where they’re enabled. On-prem clients must use a Private Endpoint with DNS forwarding.

Glossary

Next steps

AzurePrivate EndpointService EndpointPrivate LinkPrivate DNSPaaS SecurityNetworkingData Exfiltration
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading