Business Challenge
Three posts of DNS — #88, #89, #90 — existed largely because of this feature. A private endpoint is the thing that makes a storage account resolve to an address inside your network, and the resolution half of that is hard enough to get its own post later in this series.
What it is is simple, and worth stating precisely because the simplicity is the point:
“A private endpoint is a network interface that uses a private IP address from your virtual network… By enabling a private endpoint, you're bringing the service into your virtual network.”
And it is not yours to edit: “A read-only network interface is automatically created for the lifecycle of the private endpoint… assigned a dynamic private IP address… the value of the private IP address remains unchanged for the entire lifecycle.”
Dynamic allocation, permanent value. That is the useful summary of the address: you do not choose it, and once chosen it will not move.
What it changes is where the expectations go wrong, and the documentation disposes of the biggest one in a single clause that is easy to read past:
“Private endpoints provide a privately accessible IP address for the Azure service, but don't necessarily restrict public network access to it.”
Creating one adds a path. It removes nothing. The storage account that was reachable from the internet before is still reachable from the internet after, unless something separate — the service's own firewall, its public network access setting — is also changed. “We moved it behind a private endpoint” and “it is no longer publicly reachable” are different statements, and only one of them follows from deploying this resource.
Architecture
What it is, changes, and does not change
| The actual behaviour | |
|---|---|
| What it is | A read-only network interface holding a dynamic private IP from your subnet, whose “value… remains unchanged for the entire lifecycle” |
| What it changes | Reachability — a private path from the same VNet, regionally and globally peered VNets, and on-premises over VPN or ExpressRoute |
| What it does not change | Public access. “don't necessarily restrict public network access to it” — closing that is a separate action on the service |
| Direction | One way only. “Service providers don't have a routing configuration to create connections into service customers” |
| Scope | One subresource per endpoint. Storage needs “separate private endpoints to access the file and blob subresources” |
| Placement | Same region and subscription as the VNet — but the target service may be in a different region |
Limits
| Resource | Limit |
|---|---|
| Private endpoints per virtual network | 1000 |
| Private endpoints across peered virtual networks | 4000 — four VNets' worth |
| Private endpoints per subscription | 64000 |
| Private IP addresses on one private endpoint NIC | 500 |
| Private endpoints per storage account / per key vault | 200 / 64 |
| Private DNS zone groups per private endpoint | 1 |
That last row is the one #89 taught us to notice. A bare 1 in a limits table is a design constraint, not a quota.
Why This Architecture Holds Up
Because network security groups do not apply to it by default
This is the most consequential default in the feature, and it is stated as the first sentence of its own page rather than as a warning on the overview:
“By default, network policies are disabled for a subnet in a virtual network. To use network policies like user-defined routes and network security group support, network policy support must be enabled for the subnet.”
So put a private endpoint in a subnet that has an NSG, and the NSG does nothing to it. Not “allows by default” — it is not consulted. The rules you wrote are present, attached, and inert with respect to that resource.
The next sentence makes it stranger, and it is the detail that makes this hard to spot in review:
“This setting only applies to private endpoints in the subnet and affects all private endpoints in the subnet. For other resources in the subnet, access is controlled based on security rules in the network security group.”
One subnet, one NSG, two behaviours. A virtual machine in that subnet is filtered normally. A private endpoint beside it is not. Anyone auditing by reading NSG rules and subnet associations — which is how NSG audits are done — sees a correctly protected subnet and has no way to tell that half the resources in it are exempt, because the exemption lives on the subnet's privateEndpointNetworkPolicies property rather than on the NSG.
The property has four values, and the tooling does not agree on them: “The possible values for privateEndpointNetworkPolicies are Disabled, NetworkSecurityGroupEnabled, RouteTableEnabled, and Enabled.” Then: “The Azure CLI only supports the values true or false. It doesn't allow you to enable the policies selectively only for user-defined routes or network security groups.”
Two of the four states are unreachable from the CLI. That is the third time in four posts that Azure's own interfaces have disagreed about what is expressible — #88 had REST and the portal splitting TXT strings differently, #90 had the portal enforcing older limits than the API. Here the CLI cannot express a configuration the ARM schema defines.
Because it injects a /32 route that beats your default route
#78 established that Azure selects routes by longest prefix match before anything else, and #79 built an entire security posture on a 0.0.0.0/0 user-defined route sending everything to an inspection point. A private endpoint quietly defeats that:
“the /32 default route sends traffic directly to the private endpoint in accordance with the longest prefix match algorithm.”
And explicitly: “a user-defined routes default route (0.0.0.0/0) won't override private endpoint routes because it covers a broader range than the private endpoint's address space.”
So an estate that forced all traffic through a firewall is not inspecting traffic to its private endpoints. The /32 wins, the packets go straight to the endpoint, and nothing in the route table looks wrong — because the route that wins was never written by anyone.
Redirecting it takes two things, and both are easy to miss. First, route-table network policies have to be on: “ensure that network policies are enabled in the subnet hosting the private endpoint.” Second, the overriding route must be specific enough — “user-defined routes must have a prefix size that is equal to or smaller than the virtual network address space where the private endpoint is provisioned.” In practice that means a route at least as specific as the VNet's own range, not a default route.
The phrasing across the two statements is worth flagging because it reads as contradictory on a first pass: one says a prefix length “equal to or larger than” the virtual network prefix length, the other a prefix size “equal to or smaller than” the address space. Both mean the same thing — a longer prefix covers a smaller range — but the two sentences sit a few lines apart using opposite comparatives, and only the worked 0.0.0.0/0 example makes the intent unambiguous.
And when traffic does go through an appliance, the return path needs help for exactly the reason #83 gave: “Due to the variable nature of the private endpoint data-plane, it's recommended to SNAT traffic destined to a private endpoint to ensure return traffic is honored when going through an NVA.” There is an escape hatch — “This limitation can be removed using disableSnatOnPL tag in your NVA” — which is the first time this series has met a documented way to avoid the SNAT trade rather than accept it.
Because the two normal ways to debug a network path are both unavailable
Having established that NSGs may or may not apply and that an invisible /32 may be steering traffic, the obvious next step is to look at what the platform thinks is in effect. You cannot:
| Normal diagnostic | On a private endpoint |
|---|---|
| Effective routes and security rules on the NIC | “won't be displayed for the private endpoint NIC in the Azure portal” |
| NSG flow logs | “unavailable for inbound traffic destined for a private endpoint” |
Those are the two tools you would reach for, and neither works on the resource whose behaviour is least obvious. Private endpoints are not the only Azure data path with a flow-log blind spot, but they are the one most likely to be carrying regulated traffic — which is the usual reason for deploying them.
One smaller filtering surprise in the same area: “Source port filtering is interpreted as *… source port filtering isn't actively used as valid scenario of traffic filtering for traffic destined to a private endpoint.” An NSG rule that narrows source ports is accepted and then widened for you.
Because the NSG rule budget is a multiplication, not a count
Even with network policies enabled, the rules themselves have an unusual ceiling: “Destination port ranges are supported as a multiplication SourceAddressPrefixes, DestinationAddressPrefixes, and DestinationPortRanges”, capped at a “factor of 250 K”. Microsoft's own worked examples:
| Sources | × Destinations | × Port ranges | = Product | Verdict |
|---|---|---|---|---|
| 1 | 1 | 4K | 4K | Valid |
| 10 | 10 | 10 | 1 K | Valid |
| 50 | 50 | 50 | 125 K | Valid |
| 50 | 50 | 100 | 250,000 | Valid — exactly at the ceiling |
| 100 | 100 | 100 | 1,000,000 | Invalid |
Doubling the port ranges in that fourth row lands exactly on the limit; doubling the sources and destinations in the fifth blows through it by four times. The practical reading is that a rule written with address prefix lists and port ranges together scales multiplicatively, so a rule that looks like three modest lists can be rejected. The failure is at configuration time rather than runtime, which is the better of the two outcomes.
The related ASG ceiling is a plain number with a nastier failure mode: “No more than 50 members in an Application Security Group… Connection failures may occur with more than 50 members.” Not rejected — may fail, at runtime.
Because one endpoint is one subresource
A private endpoint does not bring a service into your network. It brings one target: “To access more subresources within the same Azure service, more private endpoints with corresponding targets are required. In the case of Azure Storage, for instance, you would need separate private endpoints to access the file and blob subresources.”
Storage is the sharp case because its subresource list is long: blob, table, queue, file, web and dfs, each with a _secondary variant. A storage account used for several of those needs an endpoint, an IP address and a DNS configuration for each one. That is the hidden multiplier in “we will private-endpoint our storage”, and it is why the 200-per-storage-account limit exists at all.
It also explains the DNS advice, which is the one place this post touches the problem a later post handles properly: “Multiple private endpoints can be created with the same private-link resource… the recommended practice is to use a single private endpoint for a specified private-link resource… to avoid duplicate entries or conflicts in DNS resolution.” Technically permitted, operationally discouraged, and the reason is DNS rather than networking.
Because the peered ceiling is four virtual networks, not forty
The limits look generous until you divide them. 1000 private endpoints per virtual network, 64000 per subscription — and 4000 across peered virtual networks.
That middle figure is the binding one in the architecture this series has been building. #77's hub-and-spoke exists so that spokes share connectivity, and #72 established peering as the way they join. A peered estate gets 4000 private endpoints in total — only four virtual networks' worth of the per-VNet limit — against the 64000 a subscription allows. So the per-subscription number is reachable only across unpeered networks, which is to say not in a hub-and-spoke design.
For most estates this is academic. For a platform team building private endpoints per application per environment across a large peered network, 4000 is a number to plan against rather than discover.
Because the direction is fixed, and that closes off a control
“Network connections can be initiated only by clients that are connecting to the private endpoint. Service providers don't have a routing configuration to create connections into service customers. Connections can be established in a single direction only.”
This is a genuine security property and worth recognising as one: the service cannot reach back into your network. It also removes a control you might reach for: “Outbound traffic denied from a private endpoint isn't a valid scenario, as the service provider can't originate traffic.” An outbound deny rule on a private endpoint is not a hardening measure, it is a no-op — the thing it would block cannot happen.
One consequence for service selection: the private-link resource “can be deployed in a different region” from the endpoint, while “the private endpoint must be deployed in the same region and subscription as the virtual network.” The endpoint is pinned to your network; the service is not. Cross-region private connectivity to a service works, and the latency is yours to measure.
Because the exclusions are specific and land where plans already are
Three sets of exclusions are worth reading before designing around private endpoints.
Static IPs are unsupported for “Azure Kubernetes Service (AKS)… Azure Application Gateway… HD Insight… Recovery Services Vaults… Third party Private Link services”. The address is stable for the endpoint's lifetime regardless, but it cannot be chosen — which matters when a firewall rule elsewhere has to name it.
NSG support is regionally incomplete: unavailable in “West India, Australia Central 2, South Africa West, Brazil Southeast, All Government regions, All China regions”. The sovereign-cloud exclusions are the significant ones, because those are precisely the environments where filtering a private endpoint would be a compliance requirement rather than a preference.
And a pair of prerequisites that fail unhelpfully: “You can create private endpoints only on a General Purpose v2 (GPv2) storage account”, and both subscriptions — the one with the resource and the one with the endpoint — “must be registered with the Microsoft network resource provider.” The second is the same cross-subscription registration requirement #88 noted for alias records, and it surfaces as an opaque deployment failure rather than a clear message.
Key Architecture Decisions
| Decision | Choose | Because |
|---|---|---|
| Making a service non-public | Two changes, not one | A private endpoint “doesn't necessarily restrict public network access”. Closing the public door is a separate setting on the service. |
| Filtering traffic to a private endpoint | Enable network policies on the subnet first | They are disabled by default, so the subnet's NSG is not consulted for private endpoints at all — while still filtering the VMs beside them. |
| Auditing NSG coverage | Check privateEndpointNetworkPolicies, not just NSG rules |
The exemption lives on the subnet property. An audit that reads rules and associations sees a protected subnet and cannot tell. |
| Setting network policies from automation | PowerShell or ARM, not the CLI | The CLI accepts only true/false and cannot express NetworkSecurityGroupEnabled or RouteTableEnabled. |
| Assuming forced tunnelling covers private endpoints | It does not | The endpoint's /32 beats 0.0.0.0/0 by longest prefix match. #79's posture has a hole shaped exactly like your private endpoints. |
| Routing private endpoint traffic via a firewall | Route-table policies on, plus a prefix at least as specific as the VNet | Both are required. A default route will not override the endpoint route. |
| Private endpoint traffic through an NVA | SNAT it, or set disableSnatOnPL |
The “variable nature of the private endpoint data-plane” means return traffic needs the symmetry #83 was about. The tag is the documented alternative. |
| Debugging a private endpoint path | Do not expect effective routes or flow logs | Neither is available for the private endpoint NIC. Plan the diagnostic approach before you need it. |
| NSG rules with address and port lists | Multiply before you write | Sources × destinations × port ranges is capped at 250 K. 50×50×100 is exactly valid; 100×100×100 is rejected. |
| Application security groups on the subnet | Keep under 50 members | Above 50, “connection failures may occur” — a runtime symptom, not a rejected configuration. |
| Sizing a storage private endpoint project | Count subresources, not accounts | One endpoint per target. Blob, table, queue, file, web and dfs each have a primary and a secondary. |
| Several endpoints to the same resource | Prefer one | Permitted, but the recommendation is a single endpoint per private-link resource to avoid “duplicate entries or conflicts in DNS resolution”. |
| Capacity planning in a peered estate | Plan against 4000, not 64000 | 4000 across peered virtual networks is only four VNets' worth of the 1000 per-VNet limit. The subscription figure is unreachable in hub-and-spoke. |
| An outbound deny rule on a private endpoint | Skip it | Explicitly “not a valid scenario” — the provider cannot originate traffic, so the rule blocks nothing. |
| Sovereign clouds and NSG filtering | Verify availability first | NSG support for private endpoints is unavailable in all Government and all China regions — where it would most often be mandatory. |
Closing Thought
The honest summary of what a private endpoint changes is narrower than the name suggests. It gives an Azure service an address inside your network, reachable from peered networks and on-premises, in one direction, for one subresource. That is genuinely valuable and it is the whole of it. It does not make the service private, it does not bring the service under your network controls, and it does not put the traffic through your inspection path.
Each of those three is a separate action, and two of them are defaults working against you. Network policies are off, so the subnet's NSG ignores the endpoint while filtering everything else in the same subnet. The endpoint's /32 wins longest prefix match, so the 0.0.0.0/0 route that #79 built a security posture on does not capture its traffic. Neither shows up as a misconfiguration, and the two tools you would use to find them — effective routes and flow logs — are both unavailable on this resource specifically.
So the thing to check is not whether the private endpoints exist. It is three properties that live nowhere near them: the service's public network access setting, the subnet's privateEndpointNetworkPolicies value, and whether any route more specific than the virtual network's own range exists to pull that traffic back through a firewall. An estate can have every private endpoint it planned, correctly deployed, and none of the three.
#92 takes the other side of the connection: Private Link service and consumer approval — how you publish your own service behind a private endpoint, and who gets to say yes.
Official Azure Reference
- What is a private endpoint? — the network interface definition and the read-only, dynamic-but-permanent IP address, the statement that private endpoints don't necessarily restrict public network access, single-direction connections and why an outbound deny is not a valid scenario, the per-subresource requirement with the Azure Storage subresource list, reachability from peered networks and on-premises, the same-region-and-subscription placement rule against the target's freedom to be elsewhere, the single-endpoint DNS recommendation, resource provider registration, the GPv2 storage requirement, network policy support for NSG, UDR and ASG, the connection states, the static IP exclusions, and the full NSG and UDR limitations including unavailable effective routes, absent flow logs, the 50-member ASG ceiling, the 250 K port-range multiplication with worked examples, source port filtering, regional unavailability, and the SNAT recommendation with the
disableSnatOnPLtag - Manage network policies for private endpoints — that network policies are disabled by default for a subnet, that the setting applies only to private endpoints while other resources remain filtered normally, the four
privateEndpointNetworkPoliciesvalues and the Azure CLI's inability to express the selective ones, the/32route the private endpoint propagates and the longest prefix match consequence, and the requirement that an overriding user-defined route be at least as specific as the virtual network address space - Azure subscription and service limits, networking section — private endpoints per virtual network, across peered virtual networks and per subscription, private IP addresses on a private endpoint network interface, private endpoints per storage account and per key vault, and private DNS zone groups per private endpoint
- Private endpoint DNS configuration — the zone-per-service table and the resolution design this post defers (no claims drawn from it)
- What is Azure Private Link service — the provider side of the connection, covered next (no claims drawn from it)
- Disable SNAT for Private Link service — the
disableSnatOnPLtag in detail (no claims drawn from it)
Comments