Azure Private Link, Service Endpoints, and Hub-Spoke Topology
You have VNets, subnets, and NSGs from the basics. But your security team wants to know why your storage account is accessible from the public internet, your database traffic traverses Microsoft's backbone instead of your private network, and your 12 spoke VNets have no central firewall. Time to graduate to enterprise networking — where every PaaS service gets a private IP and every packet flows through your inspection points.
Service Endpoints vs Private Endpoints
Both solve the same problem — keeping traffic to Azure PaaS services (Storage, SQL, Key Vault) off the public internet. But they work very differently.
| Feature | Service Endpoints | Private Endpoints (Private Link) |
|---|---|---|
| How it works | Adds an optimized route from subnet to PaaS service | Creates a private NIC with a VNet IP for the PaaS service |
| Traffic path | Azure backbone (still uses public IP) | Entirely within your VNet (private IP) |
| DNS | No DNS change needed | Requires Private DNS Zone |
| Cross-region | Same region only | Works across regions and tenants |
| On-premises access | Not reachable from on-prem | Reachable from on-prem via VPN/ExpressRoute |
| Cost | Free | ~$7.30/month per endpoint + data processing |
| Granularity | Subnet level (all resources of that type) | Per-resource (specific storage account) |
The recommendation: Use Private Endpoints for production workloads. They give you a real private IP address on your VNet, which means on-premises clients can reach the service through VPN/ExpressRoute, and you get full DNS control.
# Service Endpoint approach — add to a subnet
az network vnet subnet update \
--resource-group rg-network \
--vnet-name vnet-spoke-01 \
--name snet-app \
--service-endpoints Microsoft.Storage Microsoft.Sql Microsoft.KeyVault
Setting Up Private Link
Private Link creates a private endpoint — a network interface with a private IP in your VNet that maps to a specific PaaS resource.
# Create a Private Endpoint for a Storage Account
az network private-endpoint create \
--name pe-storage-prod \
--resource-group rg-network \
--vnet-name vnet-spoke-01 \
--subnet snet-private-endpoints \
--private-connection-resource-id "/subscriptions/<sub-id>/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stproddata2025" \
--group-id blob \
--connection-name "storage-prod-connection"
# Create the Private DNS Zone for blob storage
az network private-dns zone create \
--resource-group rg-network \
--name "privatelink.blob.core.windows.net"
# Link the DNS zone to your VNet
az network private-dns zone-link create \
--resource-group rg-network \
--zone-name "privatelink.blob.core.windows.net" \
--name "link-vnet-spoke-01" \
--virtual-network vnet-spoke-01 \
--registration-enabled false
# Create the DNS record for the private endpoint
az network private-endpoint dns-zone-group create \
--resource-group rg-network \
--endpoint-name pe-storage-prod \
--name "default" \
--private-dns-zone "privatelink.blob.core.windows.net" \
--zone-name "blob"
After this, any VM or service in vnet-spoke-01 resolving stproddata2025.blob.core.windows.net will get the private IP (e.g., 10.1.5.4) instead of the public IP. Traffic never leaves your network.
Hub-Spoke Network Architecture
Hub-Spoke is the standard enterprise network topology in Azure. A central hub VNet contains shared services (firewall, VPN gateway, DNS). Spoke VNets contain workloads and peer to the hub.
On-Premises
|
VPN Gateway / ExpressRoute
|
┌─────────────┐
│ Hub VNet │
│ 10.0.0.0/16 │
│ │
│ Azure Firewall│
│ Bastion Host │
│ DNS Resolver │
└──┬───┬───┬──┘
│ │ │
┌────────┘ │ └────────┐
│ │ │
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Spoke 01 │ │ Spoke 02 │ │ Spoke 03 │
│ Web Tier │ │ App Tier │ │ Data Tier│
│10.1.0/16 │ │10.2.0/16 │ │10.3.0/16 │
└──────────┘ └──────────┘ └──────────┘
# Create the hub VNet
az network vnet create \
--resource-group rg-hub \
--name vnet-hub \
--address-prefix 10.0.0.0/16 \
--subnet-name AzureFirewallSubnet \
--subnet-prefix 10.0.1.0/24
# Create a spoke VNet
az network vnet create \
--resource-group rg-spoke-01 \
--name vnet-spoke-01 \
--address-prefix 10.1.0.0/16 \
--subnet-name snet-app \
--subnet-prefix 10.1.1.0/24
# Peer spoke to hub (both directions)
az network vnet peering create \
--resource-group rg-spoke-01 \
--name spoke01-to-hub \
--vnet-name vnet-spoke-01 \
--remote-vnet "/subscriptions/<sub-id>/resourceGroups/rg-hub/providers/Microsoft.Network/virtualNetworks/vnet-hub" \
--allow-forwarded-traffic true \
--allow-gateway-transit false \
--use-remote-gateways true
az network vnet peering create \
--resource-group rg-hub \
--name hub-to-spoke01 \
--vnet-name vnet-hub \
--remote-vnet "/subscriptions/<sub-id>/resourceGroups/rg-spoke-01/providers/Microsoft.Network/virtualNetworks/vnet-spoke-01" \
--allow-forwarded-traffic true \
--allow-gateway-transit true
Key flags: --allow-gateway-transit true on the hub side lets spokes use the hub's VPN/ExpressRoute gateway. --use-remote-gateways true on the spoke side enables this.
Azure Firewall in the Hub
Azure Firewall sits in the hub VNet and inspects all traffic flowing between spokes, to the internet, and to on-premises. It supports FQDN filtering, threat intelligence, TLS inspection, and network/application rules.
# Create a public IP for the firewall
az network public-ip create \
--resource-group rg-hub \
--name pip-azfw \
--sku Standard \
--allocation-method Static
# Create the Azure Firewall
az network firewall create \
--resource-group rg-hub \
--name azfw-hub \
--location eastus \
--sku AZFW_VNet \
--tier Standard
# Configure the firewall IP
az network firewall ip-config create \
--resource-group rg-hub \
--firewall-name azfw-hub \
--name fw-config \
--public-ip-address pip-azfw \
--vnet-name vnet-hub
# Add a network rule to allow spoke-to-spoke traffic
az network firewall network-rule create \
--resource-group rg-hub \
--firewall-name azfw-hub \
--collection-name "allow-spoke-traffic" \
--name "spoke01-to-spoke02" \
--protocols TCP UDP \
--source-addresses 10.1.0.0/16 \
--destination-addresses 10.2.0.0/16 \
--destination-ports '*' \
--action Allow \
--priority 100
User Defined Routes (UDR)
By default, spoke-to-spoke traffic goes directly via VNet peering. To force traffic through the firewall for inspection, you create UDRs that override the default routes.
# Create a route table
az network route-table create \
--resource-group rg-spoke-01 \
--name rt-spoke-01 \
--disable-bgp-route-propagation true
# Add a default route pointing to the firewall
az network route-table route create \
--resource-group rg-spoke-01 \
--route-table-name rt-spoke-01 \
--name "to-firewall" \
--address-prefix 0.0.0.0/0 \
--next-hop-type VirtualAppliance \
--next-hop-ip-address 10.0.1.4 # Azure Firewall private IP
# Associate the route table with the spoke subnet
az network vnet subnet update \
--resource-group rg-spoke-01 \
--vnet-name vnet-spoke-01 \
--name snet-app \
--route-table rt-spoke-01
Now all outbound traffic from snet-app in Spoke 01 flows through Azure Firewall first. The firewall applies your rules, logs the traffic, and forwards allowed packets. This gives you a single enforcement point for all east-west and north-south traffic.
Azure Bastion for Secure VM Access
Stop exposing RDP (3389) and SSH (22) to the internet. Azure Bastion provides browser-based access to your VMs over TLS without a public IP on the VM.
# Create the AzureBastionSubnet (name and minimum /26 are required)
az network vnet subnet create \
--resource-group rg-hub \
--vnet-name vnet-hub \
--name AzureBastionSubnet \
--address-prefix 10.0.2.0/26
# Create a public IP for Bastion
az network public-ip create \
--resource-group rg-hub \
--name pip-bastion \
--sku Standard \
--allocation-method Static
# Deploy Azure Bastion
az network bastion create \
--resource-group rg-hub \
--name bastion-hub \
--vnet-name vnet-hub \
--public-ip-address pip-bastion \
--sku Standard \
--enable-tunneling true
With the Standard SKU and tunneling enabled, you can also use native SSH/RDP clients through the az network bastion tunnel command — useful for file transfers and tools that need a native connection.
Network Watcher Tools
Network Watcher is Azure's network diagnostic toolkit. It is enabled per-region and provides:
| Tool | What It Does |
|---|---|
| IP Flow Verify | Checks if a packet is allowed or denied by NSG rules |
| Next Hop | Shows the next hop for a packet from a VM |
| Connection Troubleshoot | Tests TCP connectivity between two endpoints |
| Packet Capture | Captures packets on a VM's NIC for Wireshark analysis |
| NSG Flow Logs | Logs all traffic allowed/denied by NSGs |
| Topology | Visualizes your network resources and connections |
# Verify if a VM can reach a storage account on port 443
az network watcher test-ip-flow \
--resource-group rg-spoke-01 \
--vm vm-app-01 \
--direction Outbound \
--protocol TCP \
--local 10.1.1.4:* \
--remote 10.0.5.10:443
# Check the next hop for traffic from a VM
az network watcher show-next-hop \
--resource-group rg-spoke-01 \
--vm vm-app-01 \
--source-ip 10.1.1.4 \
--dest-ip 10.2.1.4
VNet Integration for App Service
App Service runs on shared infrastructure by default, outside your VNet. VNet Integration lets your web app's outbound traffic flow through your VNet, reaching private endpoints and on-premises resources.
# Create a dedicated subnet for App Service integration
az network vnet subnet create \
--resource-group rg-spoke-01 \
--vnet-name vnet-spoke-01 \
--name snet-appservice \
--address-prefix 10.1.2.0/24 \
--delegations Microsoft.Web/serverFarms
# Integrate the App Service with the VNet
az webapp vnet-integration add \
--resource-group rg-app \
--name webapp-prod \
--vnet vnet-spoke-01 \
--subnet snet-appservice
After integration, your App Service can reach Private Endpoints (databases, storage, Key Vault) using their private IPs. Combine this with access restrictions to block public inbound traffic, and your App Service becomes fully private.
ExpressRoute Overview
ExpressRoute is a dedicated private connection between your on-premises network and Azure — it does not traverse the public internet. Connections go through a connectivity partner (AT&T, Equinix, Megaport) at a peering location.
| Feature | VPN Gateway | ExpressRoute |
|---|---|---|
| Connection | Over public internet (encrypted) | Private dedicated link |
| Bandwidth | Up to 10 Gbps | Up to 100 Gbps |
| Latency | Variable | Predictable, low |
| Encryption | IPSec built-in | Optional (MACsec) |
| Cost | ~$140-800/month | ~$200-10,000+/month |
| Best For | Small offices, dev/test | Production hybrid, data-heavy |
ExpressRoute circuits connect to your hub VNet's ExpressRoute Gateway. Because spokes peer to the hub with use-remote-gateways, they automatically get on-premises connectivity without additional configuration.
Wrapping Up
Enterprise Azure networking comes down to three principles: keep traffic private (Private Link for PaaS, Bastion for management), centralize inspection (Hub-Spoke with Azure Firewall and UDRs), and integrate fully (VNet Integration for PaaS, ExpressRoute for on-premises). Start with a hub VNet containing your firewall and bastion, peer your workload spokes, and add private endpoints for every PaaS service. Use Network Watcher when things break. The result is a network where nothing is exposed that should not be, and every packet has a clear, auditable path.
Next up: We will explore Microsoft Defender for Cloud — how to assess your security posture, fix vulnerabilities, and protect workloads across compute, storage, databases, and containers.
