Theoretical Foundation: Configure Private Endpoints for Azure PaaS
1. Initial Intuitionβ
In the previous module about Service Endpoints, we saw that they create an optimized path from the VNet to the PaaS service, but the service maintains its public IP address. It's like having an exclusive entrance to a bank, but the bank still has an address on the main street that anyone can visit.
Private Endpoints go much further: they bring the PaaS service inside your VNet. It's as if the bank opened an exclusive branch inside your condominium, with an internal address that only residents know and only they can visit. The original bank continues to exist on the main street, but your private branch is only accessible from inside.
In practice: a Private Endpoint creates a network interface (NIC) with a private IP from your VNet that represents a specific PaaS service (like a Storage Account or a SQL Database). When your VMs connect to the service, DNS resolves the service name to this private IP, and traffic never leaves your private network.
2. Contextβ
2.1 The evolution of private access to PaaSβ
2.2 Why Private Endpoints are superior to Service Endpointsβ
Private Endpoints solve limitations that Service Endpoints cannot address:
- On-premises access: VMs or servers connected via VPN or ExpressRoute can access PaaS services as if they were internal resources
- Private DNS: The name
myaccount.blob.core.windows.netresolves to a private IP in the VNet, not to a public IP - Instance-level isolation: Each Private Endpoint points to a specific service instance, not to the entire Azure Storage service
- Elimination of public endpoint: It's possible to completely disable public access to the PaaS service
2.3 Main use casesβ
- Banking and healthcare applications with regulated data that can never travel over public networks
- On-premises environments connected via ExpressRoute that need to access Azure SQL or Azure Storage
- Architectures where the PaaS service should not be visible outside the VNet
- Multi-tenant environments where different clients have completely isolated PaaS services
3. Concept Constructionβ
3.1 The three components of a Private Endpointβ
1. Private Endpoint (the resource) An Azure object that represents the private connection to a service. Contains: the NIC with private IP, the reference to the target service, and the target sub-resource.
2. Network Interface (NIC) Created automatically along with the Private Endpoint. Receives a private IP from the subnet where it's deployed. It's through this NIC that traffic actually flows.
3. Private Link Connection The logical connection between the Private Endpoint and the PaaS service. May require approval by the service owner, depending on configuration.
3.2 Sub-resources: connecting to the right resource within the serviceβ
Many PaaS services have multiple sub-resources. When you create a Private Endpoint, you need to specify which sub-resource you want to expose. Examples:
| Service | Available sub-resources |
|---|---|
| Azure Storage | blob, file, queue, table, dfs, web |
| Azure SQL Database | sqlServer |
| Azure Key Vault | vault |
| Azure Cosmos DB | Sql, MongoDB, Cassandra, Gremlin, Table |
| Azure App Service | sites |
| Azure Container Registry | registry |
| Azure Event Hubs Namespace | namespace |
Important point: If you need a Storage Account to be accessed via both Blob and File through private networks, you need two Private Endpoints, one for the
blobsub-resource and another forfile.
3.3 Private Link Service vs Private Endpointβ
There's a distinction that often causes confusion:
Private Endpoint: The consumer side. You create it in your VNet to access a service from another party (Azure PaaS or partner service).
Private Link Service: The provider side. An organization creates it to expose their own services (running behind a Standard Load Balancer) to other VNets or tenants via Private Endpoints.
For the AZ-104 objective, the focus is on Private Endpoints connecting to Microsoft-managed PaaS services.
3.4 The connection approval processβ
When a Private Endpoint is created pointing to a PaaS service, the connection can have different states:
Auto-approval: When the Private Endpoint and target service are in the same Azure subscription or the creator has Microsoft.Network/privateEndpoints/privateLinkServiceConnections/write permission on the service, approval is automatic.
Manual approval: When the service belongs to another tenant or another subscription without the above permissions, the service owner must manually approve in the Private endpoint connections panel of the resource.
3.5 Private DNS Zone: the most critical piece of the puzzleβ
This is the most important and least intuitive component of Private Endpoints.
The problem: The name myaccount.blob.core.windows.net publicly resolves to a public IP. If your VMs use this name and it resolves to the public IP, traffic will go over the internet even if you have a Private Endpoint.
The solution: A Private DNS Zone that overrides DNS resolution for this name within your VNet, making it resolve to the Private Endpoint's private IP.
Each PaaS service has a specific private DNS zone:
| Service | Sub-resource | Private DNS zone |
|---|---|---|
| Azure Storage (Blob) | blob | privatelink.blob.core.windows.net |
| Azure Storage (File) | file | privatelink.file.core.windows.net |
| Azure Storage (Queue) | queue | privatelink.queue.core.windows.net |
| Azure Storage (Table) | table | privatelink.table.core.windows.net |
| Azure Storage (ADLS Gen2) | dfs | privatelink.dfs.core.windows.net |
| Azure SQL Database | sqlServer | privatelink.database.windows.net |
| Azure Key Vault | vault | privatelink.vaultcore.azure.net |
| Azure Cosmos DB (SQL) | Sql | privatelink.documents.azure.com |
| Azure Container Registry | registry | privatelink.azurecr.io |
| Azure App Service | sites | privatelink.azurewebsites.net |
The mechanism works like this: when you resolve myaccount.blob.core.windows.net from within the VNet:
- The DNS query goes to the VNet's DNS resolver (168.63.129.16)
- The resolver checks if there's a Private DNS Zone linked to the VNet for
privatelink.blob.core.windows.net - If yes, and if there's an A record for
myaccount.privatelink.blob.core.windows.netβ returns the Private Endpoint's private IP - Traffic goes to the private IP (the Private Endpoint's NIC), never leaving the VNet
4. Structural Viewβ
5. Practical Operationβ
5.1 Complete configuration flowβ
Most forgotten step: Step 4, linking the Private DNS Zone to the VNet. The zone can exist but if it's not linked to the VNet, the VNet's DNS resolver doesn't use it and the name continues resolving to the public IP.
5.2 The Azure portal creates the DNS Zone automaticallyβ
When you create a Private Endpoint through the portal and select "Integrate with private DNS zone", the portal:
- Creates the Private DNS Zone with the correct name automatically
- Creates the VNet link automatically
- Creates the A record pointing to the private IP automatically
This greatly simplifies the process. Via CLI and IaC, each step is separate and manual.
5.3 Verifying DNS resolutionβ
After configuring, verify that DNS is correct from within a VM in the VNet:
# On Linux (inside the VM)
nslookup myaccount.blob.core.windows.net
# Expected result:
# myaccount.blob.core.windows.net β myaccount.privatelink.blob.core.windows.net β 10.0.2.5
# (The intermediate CNAME with "privatelink" is normal and expected)
The CNAME to privatelink.blob.core.windows.net is created automatically by Microsoft in public DNS. The Private DNS Zone intercepts the resolution of this CNAME and returns the private IP.
5.4 On-premises access via ExpressRoute/VPNβ
For on-premises servers to access the PaaS service via Private Endpoint:
- The Private Endpoint must exist and be approved
- The Private DNS Zone must be linked to the VNet
- On-premises DNS needs to be configured to forward queries for
privatelink.blob.core.windows.netto the VNet's DNS resolver (168.63.129.16)
This DNS forwarding configuration is the most complex point in hybrid environments. Azure Private DNS Resolver simplifies this by creating inbound and outbound endpoints for DNS resolution.
6. Implementation Methodsβ
6.1 Azure Portalβ
When to use: Initial creation, simple environments, when you want automatic DNS Zone creation.
Creation: Private Link Center > Private endpoints > + Create
OR from the storage Account: Storage Account > Networking > Private endpoint connections > + Private endpoint
The form goes through tabs:
- Basics: Name, region, resource group
- Resource: Resource type, target resource, sub-resource
- Virtual Network: VNet, subnet, IP configuration
- DNS: Integration with Private DNS Zone (recommended to check "Yes")
- Tags and Review
6.2 Azure CLIβ
Creating the Private Endpoint:
# Get Storage Account Resource ID
STORAGE_ID=$(az storage account show \
--name myaccount \
--resource-group myRG \
--query id --output tsv)
# Create the Private Endpoint
az network private-endpoint create \
--name pe-myaccount-blob \
--resource-group myRG \
--vnet-name myVNet \
--subnet PrivateEndpointSubnet \
--private-connection-resource-id $STORAGE_ID \
--group-id blob \
--connection-name conn-myaccount-blob \
--location eastus
Creating the Private DNS Zone:
az network private-dns zone create \
--resource-group myRG \
--name "privatelink.blob.core.windows.net"
Linking the DNS Zone to VNet:
az network private-dns link vnet create \
--resource-group myRG \
--zone-name "privatelink.blob.core.windows.net" \
--name link-to-myVNet \
--virtual-network myVNet \
--registration-enabled false
Creating the DNS record automatically using DNS Zone Group:
# Get the Private Endpoint Resource ID
PE_ID=$(az network private-endpoint show \
--name pe-myaccount-blob \
--resource-group myRG \
--query id --output tsv)
# Create DNS Zone Group (creates A record automatically)
az network private-endpoint dns-zone-group create \
--resource-group myRG \
--endpoint-name pe-myaccount-blob \
--name dns-zone-group \
--private-dns-zone "privatelink.blob.core.windows.net" \
--zone-name zone1
DNS Zone Group is the mechanism that automatically creates the A record in the Private DNS Zone and keeps it synchronized if the Private Endpoint's IP changes.
Disabling public access to Storage Account:
az storage account update \
--name myaccount \
--resource-group myRG \
--public-network-access Disabled
Checking connection status:
az network private-endpoint show \
--name pe-myaccount-blob \
--resource-group myRG \
--query "privateLinkServiceConnections[].privateLinkServiceConnectionState"
6.3 Azure PowerShellβ
# Complete configuration via PowerShell
# 1. Create Private Endpoint
$storage = Get-AzStorageAccount -ResourceGroupName "myRG" -Name "myaccount"
$privateEndpointConnection = New-AzPrivateLinkServiceConnection `
-Name "conn-myaccount-blob" `
-PrivateLinkServiceId $storage.Id `
-GroupId "blob"
$subnet = Get-AzVirtualNetworkSubnetConfig `
-VirtualNetwork (Get-AzVirtualNetwork -Name "myVNet" -ResourceGroupName "myRG") `
-Name "PrivateEndpointSubnet"
$privateEndpoint = New-AzPrivateEndpoint `
-ResourceGroupName "myRG" `
-Name "pe-myaccount-blob" `
-Location "eastus" `
-Subnet $subnet `
-PrivateLinkServiceConnection $privateEndpointConnection
# 2. Create and configure Private DNS Zone
$zone = New-AzPrivateDnsZone `
-ResourceGroupName "myRG" `
-Name "privatelink.blob.core.windows.net"
New-AzPrivateDnsVirtualNetworkLink `
-ResourceGroupName "myRG" `
-ZoneName "privatelink.blob.core.windows.net" `
-Name "link-to-myVNet" `
-VirtualNetworkId (Get-AzVirtualNetwork -Name "myVNet" -ResourceGroupName "myRG").Id `
-EnableRegistration $false
# 3. Create DNS Zone Group
New-AzPrivateEndpointIpConfiguration `
-Name "dns-zone-group" `
-PrivateDnsZoneId $zone.ResourceId `
-RecordType A
6.4 Bicepβ
// Private Endpoint for Storage Account (Blob)
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
name: 'pe-myaccount-blob'
location: location
properties: {
subnet: {
id: privateEndpointSubnet.id
}
privateLinkServiceConnections: [
{
name: 'conn-myaccount-blob'
properties: {
privateLinkServiceId: storageAccount.id
groupIds: ['blob']
}
}
]
}
}
// Private DNS Zone
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.blob.core.windows.net'
location: 'global'
}
// VNet Link
resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: 'link-to-vnet'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: vnet.id
}
}
}
// DNS Zone Group (creates A record automatically)
resource dnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
parent: privateEndpoint
name: 'dns-zone-group'
properties: {
privateDnsZoneConfigs: [
{
name: 'config-blob'
properties: {
privateDnsZoneId: privateDnsZone.id
}
}
]
}
}
6.5 Approving pending connection (cross-tenant)β
When the Private Endpoint points to a service in another tenant or subscription without adequate permissions:
# On the provider side (service owner), list pending connections
az storage account show \
--name myaccount \
--resource-group myRG \
--query privateEndpointConnections
# Approve the connection
az network private-endpoint-connection approve \
--resource-group myRG \
--resource-name myaccount \
--type Microsoft.Storage/storageAccounts \
--name <connection-name>
7. Control and Securityβ
7.1 Required permissionsβ
| Operation | Minimum permission |
|---|---|
| Create Private Endpoint | Network Contributor on VNet + Reader on PaaS service |
| Approve Private Endpoint connection | Contributor or Owner on PaaS service |
| Create Private DNS Zone | Private DNS Zone Contributor |
| Link DNS Zone to VNet | Network Contributor on VNet |
| Disable public access to PaaS | Contributor on PaaS resource |
7.2 NSG and Private Endpointsβ
By default, NSGs do not apply to Private Endpoints. Traffic to the Private Endpoint's private IP passes through the subnet NSG but the rules are not evaluated.
To enable NSG on Private Endpoints (newer functionality):
az network vnet subnet update \
--vnet-name myVNet \
--name PrivateEndpointSubnet \
--resource-group myRG \
--private-endpoint-network-policies Enabled
After enabling, NSGs and UDRs applied to the subnet affect traffic to Private Endpoints, allowing additional microsegmentation.
7.3 Disabling public access to the PaaS serviceβ
This is the final step for maximum security. After creating the Private Endpoint, the PaaS service still accepts public connections by default. To disable:
Storage Account:
az storage account update \
--name myaccount \
--resource-group myRG \
--public-network-access Disabled
Azure SQL Database:
az sql server update \
--name mySqlServer \
--resource-group myRG \
--enable-public-network false
Key Vault:
az keyvault update \
--name myKeyVault \
--resource-group myRG \
--public-network-access Disabled
Warning: Before disabling public access, ensure that all applications and administrative tools accessing the service are already using the Private Endpoint. Premature disabling causes interruptions.
8. Decision Makingβ
8.1 Private Endpoint vs Service Endpoint: definitive decisionβ
| Criteria | Service Endpoint | Private Endpoint |
|---|---|---|
| Service IP | Public (immutable) | Private (in VNet) |
| On-premises access | No | Yes |
| Cost | Free | Hourly cost + data |
| DNS configuration | Not required | Mandatory |
| Disable public endpoint | Optional, but PaaS still accessible | Can disable completely |
| Isolation per instance | No (per service) | Yes (per specific resource) |
| Complexity | Low | Medium/High |
| Transitivity via VNet peering | No | Yes (traffic via route) |
| Regulatory support (HIPAA, PCI-DSS) | Partial | Complete |
8.2 When each approach is mandatory vs recommendedβ
| Situation | Approach | Justification |
|---|---|---|
| ExpressRoute access to Storage | Private Endpoint (mandatory) | Service Endpoint is not transitive via ExpressRoute |
| Azure VM accesses SQL Database, no on-premises | Service Endpoint or Private Endpoint | Both work; PE if compliance requirement |
| PCI-DSS or HIPAA compliance | Private Endpoint (mandatory) | Data cannot traverse public networks at all |
| Multi-tenant environment with complete isolation | Private Endpoint (mandatory) | Service Endpoint doesn't isolate per instance |
| Minimum cost in dev/test | Service Endpoint | PE has additional cost |
| Private DNS resolution required | Private Endpoint (mandatory) | Service Endpoint doesn't change DNS |
8.3 Dedicated subnet for Private Endpointsβ
| Situation | Use dedicated subnet? | Reason |
|---|---|---|
| Multiple Private Endpoints | Yes (recommended) | Clearer organization and NSG management |
| Single Private Endpoint | Optional | Can coexist with other VMs if NSG is managed |
| Environment with NSG on PE enabled | Yes | Facilitates creation of specific rules |
9. Best Practicesβ
- Always create the DNS Zone Group when creating the Private Endpoint via CLI/PowerShell/Bicep. Without it, the A record in the Private DNS Zone is not created automatically and DNS doesn't work.
- Verify DNS resolution from within a VM in the VNet after configuration. An
nslookupthat returns a private IP is the fastest sanity check. - Disable public access to the PaaS service after validating that all applications work via Private Endpoint. Keep enabled during transition.
- Use dedicated subnets for Private Endpoints in production, especially when there are many endpoints.
- Centralize Private DNS Zones in the hub in hub-and-spoke architectures and link to spoke VNets. Don't create duplicate zones in each VNet.
- Configure DNS forwarding for on-premises environments: local DNS servers should forward queries for
privatelink.*to the Azure VNet DNS resolver. - Use Azure Private DNS Resolver in complex hybrid environments to centralize DNS resolution between on-premises and Azure.
- Enable NSG on Private Endpoints in high-security environments to control which traffic can reach the endpoint.
- Document which private IP was assigned to each Private Endpoint. IPs don't change after creation but it's important to record for troubleshooting.
10. Common Errorsβ
| Error | Why it happens | How to avoid |
|---|---|---|
| DNS resolves public IP even with PE created | DNS Zone not linked to VNet or DNS Zone Group not created | Check VNet link and A record in Private DNS Zone |
| Application can't connect even with correct DNS | Public access disabled before PE is approved | Wait for approval and check connection state |
| Connection timeout from on-premises | On-premises DNS doesn't forward privatelink.* queries to Azure | Configure DNS forwarder to the VNet |
| PE in "Pending" state indefinitely | Creator doesn't have auto-approval permission on the service | Manually approve in the PaaS service panel |
| Multiple DNS Zones created accidentally | Each deployment creates a new zone instead of reusing | Check if the zone already exists before creating |
| NSG blocking traffic to PE | private-endpoint-network-policies enabled without adequate rules | Create Allow rules in NSG for traffic to PE IP |
| A record not created in Private DNS Zone | DNS Zone Group was not created | Create DNS Zone Group after creating Private Endpoint |
| Access from spoke VNet doesn't work | DNS Zone not linked to spoke VNet | Add VNet link from zone to each VNet that needs to resolve |
11. Operations and Maintenanceβ
11.1 Checking the state of all Private Endpoint connectionsβ
# List all Private Endpoints and their states
az network private-endpoint list \
--resource-group myRG \
--query "[].{Name:name, Status:privateLinkServiceConnections[0].privateLinkServiceConnectionState.status, ProvisioningState:provisioningState}" \
--output table
11.2 Checking the created DNS recordβ
az network private-dns record-set a list \
--resource-group myRG \
--zone-name "privatelink.blob.core.windows.net" \
--output table
11.3 Monitoring Private Endpoints with Azure Monitorβ
az monitor diagnostic-settings create \
--name "pe-diagnostics" \
--resource <private-endpoint-id> \
--metrics '[{"category": "AllMetrics", "enabled": true}]' \
--workspace <log-analytics-workspace-id>
Relevant metrics include bytes sent/received by the Private Endpoint.
11.4 Important limitsβ
| Resource | Limit |
|---|---|
| Private Endpoints per subscription | 64,000 |
| Private Endpoints per VNet | No documented limit |
| Private DNS Zones per subscription | 1,000 |
| VNet links per Private DNS Zone | 1,000 |
| A records per Private DNS Zone | 25,000 |
| Cost | ~$7.30/month per endpoint + $0.01 per GB processed (approximate, varies by region) |
12. Integration and Automationβ
12.1 Azure Policy to require Private Endpointsβ
# Policy: Storage Accounts must use Private Endpoints
az policy assignment create \
--name "storage-require-private-endpoint" \
--policy "6edd7eda-6dd8-40f7-810d-67160c639cd9" \
--scope "/subscriptions/<sub-id>"
Custom policy to deny creation of Storage Accounts with public access enabled:
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/publicNetworkAccess",
"notEquals": "Disabled"
}
]
},
"then": {
"effect": "deny"
}
}
12.2 Azure Private DNS Resolver for hybrid environmentsβ
# Create DNS Resolver
az dns-resolver create \
--name myDnsResolver \
--resource-group myRG \
--location eastus \
--id-virtual-network <vnet-id>
# Create inbound endpoint (on-premises DNS points to this IP)
az dns-resolver inbound-endpoint create \
--dns-resolver-name myDnsResolver \
--resource-group myRG \
--name inbound-ep \
--ip-configurations "[{\"privateIpAllocationMethod\":\"Dynamic\",\"id\":\"<subnet-id>\"}]"
# Create outbound endpoint and forwarding ruleset
az dns-resolver outbound-endpoint create \
--dns-resolver-name myDnsResolver \
--resource-group myRG \
--name outbound-ep \
--id <subnet-id>
The Private DNS Resolver is Microsoft's recommended solution for hybrid DNS resolution, replacing the traditional "custom DNS server" with manual forwarders.
13. Final Summaryβ
Essential concepts:
- A Private Endpoint creates a NIC with private IP in your VNet that represents a specific PaaS service. Traffic to the service never leaves the private network.
- Configuration requires three components: the Private Endpoint, the Private DNS Zone (with VNet link), and the DNS Zone Group (which creates the A record automatically).
- Each sub-resource of a PaaS service (blob, file, queue in Storage; sqlServer in SQL) requires a separate Private Endpoint.
The DNS chain is the central piece:
myaccount.blob.core.windows.net β CNAME β myaccount.privatelink.blob.core.windows.net β A record in Private DNS Zone β Private Endpoint private IP (e.g., 10.0.2.5)
Critical differences with Service Endpoints:
- Service Endpoint: traffic uses MS backbone but PaaS has public IP. Doesn't work from on-premises.
- Private Endpoint: PaaS receives private IP in VNet. Works from on-premises via VPN/ExpressRoute. Has additional cost. Requires DNS configuration.
What needs to be remembered:
- Without the Private DNS Zone linked to the VNet, DNS resolves the public IP even with the Private Endpoint active.
- The DNS Zone Group is the mechanism that creates and synchronizes the A record automatically.
- The connection may need manual approval when the PaaS service is in another tenant or subscription without adequate permissions.
- Disabling public access to the PaaS is the final step for complete security, but should be done after validating that everything works through the private endpoint.
- For on-premises environments, DNS resolution requires configuring forwarders pointing to Azure DNS (
168.63.129.16) or using Azure Private DNS Resolver. - By default, NSGs don't apply to Private Endpoints; it's necessary to enable
private-endpoint-network-policieson the subnet to activate them.