Azure Hub-Spoke Networking — Production VNet Architecture
Design production Azure networking — Hub-Spoke topology, Azure Firewall, NSGs, Private Endpoints, DNS resolution, VPN Gateway and ExpressRoute, and landing zones. With Bicep examples throughout.
Azure networking is where production architectures either hold under pressure or collapse. A poorly designed VNet topology produces security gaps, inter-service latency, and compliance failures. This guide covers the patterns that Azure platform teams actually use: Hub-Spoke topology, Azure Firewall, Private Endpoints, DNS resolution, and how it all fits into a Landing Zone.
Why Topology Matters
In a flat VNet model (single VNet, everything peers directly), you have:
- No central egress point — traffic control is impossible at scale
- No shared services model — every spoke deploys its own Firewall, Bastion, DNS
- No isolation between workloads — a compromised spoke can reach everything
Hub-Spoke solves this by separating shared infrastructure from workloads:
┌─────────────────────────────┐
│ Hub VNet │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Azure │ │ VPN / │ │
│ │ Firewall │ │ExpressRte│ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Azure │ │ Private │ │
│ │ Bastion │ │ DNS │ │
│ └──────────┘ └──────────┘ │
└───────────┬─────────────────┘
VNet Peering│(non-transitive)
┌──────────────────┼──────────────────┐
│ │ │
┌────────────────┐ ┌───────────────┐ ┌───────────────┐
│ Spoke: Prod │ │ Spoke: Staging│ │ Spoke: Shared │
│ │ │ │ │ Services │
│ App Tier │ │ App Tier │ │ ACR, AKV │
│ Data Tier │ │ Data Tier │ │ Storage │
└────────────────┘ └───────────────┘ └───────────────┘All spokes peer to the hub. Spokes do not peer to each other — traffic between spokes must transit the hub (through Azure Firewall), giving you a single point of inspection for all east-west traffic.
Hub VNet Design
Address Space Planning
Plan IP space before you deploy. Re-addressing VNets is painful.
Hub VNet: 10.0.0.0/16
AzureFirewallSubnet: 10.0.0.0/26 (required name, min /26)
GatewaySubnet: 10.0.1.0/27 (required name for VPN/ExpressRoute)
AzureBastionSubnet: 10.0.2.0/26 (required name, min /26)
DNSSubnet: 10.0.3.0/28 (custom DNS resolvers)
Production Spoke: 10.1.0.0/16
AppSubnet: 10.1.1.0/24
DataSubnet: 10.1.2.0/24
PrivateEndpointSubnet: 10.1.3.0/24
Staging Spoke: 10.2.0.0/16
(same structure, different /16)
Shared Services: 10.3.0.0/16
ContainerRegistry: 10.3.1.0/24
KeyVaultSubnet: 10.3.2.0/24Rule: never use overlapping address spaces. VNet peering fails if address spaces overlap. Azure-reserved ranges (169.254.0.0/16, 224.0.0.0/4, 255.255.255.255/32) cannot be used.
Hub Bicep (Core Components)
// hub-vnet.bicep
param location string = resourceGroup().location
param hubAddressPrefix string = '10.0.0.0/16'
resource hubVnet 'Microsoft.Network/virtualNetworks@2023-04-01' = {
name: 'vnet-hub'
location: location
properties: {
addressSpace: { addressPrefixes: [hubAddressPrefix] }
subnets: [
{
name: 'AzureFirewallSubnet'
properties: { addressPrefix: '10.0.0.0/26' }
}
{
name: 'GatewaySubnet'
properties: { addressPrefix: '10.0.1.0/27' }
}
{
name: 'AzureBastionSubnet'
properties: { addressPrefix: '10.0.2.0/26' }
}
]
}
}Azure Firewall: Central Egress Control
Azure Firewall is a managed, stateful L4/L7 firewall deployed in the hub. All spoke egress traffic routes through it via User Defined Routes (UDRs).
Spoke VM/App → UDR: 0.0.0.0/0 → Azure Firewall private IP
Azure Firewall → inspect → allow/deny
→ internet egress (if allowed)Firewall Policy Structure
// Azure Firewall Premium (IDPS, TLS inspection)
resource firewallPolicy 'Microsoft.Network/firewallPolicies@2023-04-01' = {
name: 'fw-policy-prod'
location: location
properties: {
sku: { tier: 'Premium' }
threatIntelMode: 'Alert'
intrusionDetection: {
mode: 'Deny' // IDPS: block known malicious traffic
}
dnsSettings: {
enableProxy: true // Firewall acts as DNS proxy for FQDN rules
}
}
}
resource networkRuleCollection 'Microsoft.Network/firewallPolicies/ruleCollectionGroups@2023-04-01' = {
parent: firewallPolicy
name: 'DefaultNetworkRuleCollectionGroup'
properties: {
priority: 200
ruleCollections: [
{
ruleCollectionType: 'FirewallPolicyFilterRuleCollection'
name: 'AllowAzureServices'
priority: 100
action: { type: 'Allow' }
rules: [
{
ruleType: 'NetworkRule'
name: 'AllowAzureMonitor'
protocols: ['TCP']
sourceAddresses: ['10.0.0.0/8']
destinationAddresses: ['AzureMonitor'] // Service Tag
destinationPorts: ['443']
}
]
}
]
}
}UDR: Force All Egress Through Firewall
// User Defined Route — applied to spoke subnets
resource routeTable 'Microsoft.Network/routeTables@2023-04-01' = {
name: 'rt-spoke-prod'
location: location
properties: {
routes: [
{
name: 'default-to-firewall'
properties: {
addressPrefix: '0.0.0.0/0'
nextHopType: 'VirtualAppliance'
nextHopIpAddress: azureFirewall.properties.ipConfigurations[0].properties.privateIPAddress
}
}
]
disableBgpRoutePropagation: false
}
}Associate this route table with every spoke subnet. Without it, traffic goes directly to internet — bypassing the firewall entirely.
Network Security Groups (NSGs)
NSGs provide subnet and NIC-level packet filtering. They are complementary to Azure Firewall — NSGs control intra-VNet traffic, Firewall controls inter-spoke and internet traffic.
resource appNsg 'Microsoft.Network/networkSecurityGroups@2023-04-01' = {
name: 'nsg-app-tier'
location: location
properties: {
securityRules: [
{
name: 'AllowHTTPS'
properties: {
priority: 100
protocol: 'Tcp'
access: 'Allow'
direction: 'Inbound'
sourceAddressPrefix: 'AzureFrontDoor.Backend' // Service Tag: only AFD
sourcePortRange: '*'
destinationAddressPrefix: '*'
destinationPortRange: '443'
}
}
{
name: 'DenyAllInbound'
properties: {
priority: 4096
protocol: '*'
access: 'Deny'
direction: 'Inbound'
sourceAddressPrefix: '*'
sourcePortRange: '*'
destinationAddressPrefix: '*'
destinationPortRange: '*'
}
}
]
}
}NSG design rules:
- Deny-all default (explicit allow only)
- Use Service Tags (
AzureLoadBalancer,AzureFrontDoor.Backend,AzureMonitor) instead of IP ranges — they auto-update - Apply NSGs at subnet level, not NIC level (unless you need per-VM rules)
- Enable NSG Flow Logs → Log Analytics for traffic analysis
Private Endpoints: PaaS on Private IP
Private Endpoints assign a private IP inside your VNet to any PaaS service. Traffic never leaves the Microsoft backbone, and you can disable the public endpoint entirely.
Without Private Endpoint:
App → internet → sql.database.windows.net (public IP) → SQL
With Private Endpoint:
App → VNet → private IP (10.1.3.4) → SQL
(Microsoft backbone, never public internet)// Private Endpoint for Azure SQL
resource sqlPe 'Microsoft.Network/privateEndpoints@2023-04-01' = {
name: 'pe-sql-prod'
location: location
properties: {
subnet: { id: privateEndpointSubnet.id }
privateLinkServiceConnections: [{
name: 'sql-link'
properties: {
privateLinkServiceId: sqlServer.id
groupIds: ['sqlServer']
}
}]
}
}
// Private DNS Zone — resolves sql hostname to private IP
resource sqlPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.database.windows.net'
location: 'global'
}
// Link DNS zone to Hub VNet so all spokes resolve via hub DNS
resource dnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: sqlPrivateDnsZone
name: 'hub-link'
location: 'global'
properties: {
virtualNetwork: { id: hubVnet.id }
registrationEnabled: false
}
}Private DNS Resolution Architecture
App in Spoke VNet
│ nslookup mydb.database.windows.net
▼
Hub VNet DNS (168.63.129.16 via Azure Firewall DNS proxy)
│ query: mydb.database.windows.net
▼
Azure Private DNS Zone: privatelink.database.windows.net
│ CNAME: mydb.privatelink.database.windows.net
▼
A record: 10.1.3.4 (Private Endpoint IP)
│
▼
App connects to 10.1.3.4 (stays in VNet)Critical: if DNS resolution is wrong (returns the public IP instead of the private endpoint IP), traffic bypasses the private endpoint even if it exists. Always verify with nslookup or Resolve-DnsName from inside the VNet.
VPN Gateway vs ExpressRoute
| | VPN Gateway | ExpressRoute | |--|------------|-------------| | Connectivity | IPsec over public internet | Private circuit to Microsoft backbone | | Bandwidth | Up to 10 Gbps | 50 Mbps to 100 Gbps | | Latency | 20–50ms | Under 10ms (SLA-backed) | | Reliability | Internet-dependent | 99.95% SLA | | Cost | Low-Medium | High | | Setup time | Hours | 4–8 weeks (carrier provisioning) | | Use case | Branch offices, dev/test, SMB | Production enterprise, regulated data |
When ExpressRoute is required (not optional):
- Financial services with data sovereignty requirements
- Healthcare with PHI that must not traverse public internet
- Compliance mandates (PCI-DSS, ISO 27001 scope requirements)
- Latency-sensitive on-premises integrations
ExpressRoute Design: Always Use Two Circuits
On-premises Azure
DC1 ──[Circuit 1]──► West Europe (primary)
DC2 ──[Circuit 2]──► North Europe (failover)
Both circuits active (active/active)
Azure BGP routes between them automatically on failure
RTO: ~30 seconds (BGP convergence)Single ExpressRoute circuit = single point of failure. Production always requires circuit redundancy.
Azure Landing Zones: Governance at Scale
A Landing Zone is the scaffolding for operating Azure at enterprise scale: management group hierarchy, subscription model, policy assignment, and hub networking.
Management Group Hierarchy:
Tenant Root
└── Contoso (root MG)
├── Platform
│ ├── Connectivity (Hub VNet subscription)
│ ├── Identity (Entra ID, AAD DS)
│ └── Management (Log Analytics, Automation)
├── Landing Zones
│ ├── Corp (VNet-connected workloads)
│ │ ├── Production subscription
│ │ └── Staging subscription
│ └── Online (internet-facing, no VNet to hub)
├── Sandbox (developer experimentation)
└── DecommissionedWhy Separate Subscriptions?
- Blast radius isolation: a subscription-level failure, misconfiguration, or security incident is contained to that subscription
- Quota isolation: subscription-level limits (VM cores, Public IPs) per subscription
- Billing separation: chargeback per subscription is straightforward
- Policy scope: different compliance policies for production vs dev
Connectivity subscription owns the Hub VNet, Azure Firewall, ExpressRoute/VPN. It is managed by the platform team. Workload teams operate in their own spoke subscriptions and peer to the hub.
NSG Flow Logs and Traffic Analytics
NSG Flow Logs capture all traffic (allowed and denied) at the subnet level. Traffic Analytics in Azure Monitor analyses them:
// KQL: Top talkers in the last 24h
AzureNetworkAnalytics_CL
| where TimeGenerated > ago(24h)
| where FlowStatus_s == 'A' // Allowed flows
| summarize BytesSent = sum(BytesSent_d)
by SrcIP_s, DestIP_s, DestPort_d
| top 20 by BytesSentEnable for every NSG in production. The data cost is low; the incident investigation value is high.
Production Checklist
- [ ] Hub VNet with Azure Firewall, Bastion, VPN/ExpressRoute Gateway deployed in Connectivity subscription
- [ ] Spoke VNets peered to hub (not to each other)
- [ ] UDR on every spoke subnet: 0.0.0.0/0 → Azure Firewall
- [ ] NSGs on all subnets with deny-all default
- [ ] Private Endpoints for all PaaS (SQL, Service Bus, Key Vault, Storage, ACR)
- [ ] Private DNS Zones linked to Hub VNet
- [ ] Azure Firewall DNS proxy enabled
- [ ] NSG Flow Logs enabled → Log Analytics
- [ ] ExpressRoute dual-circuit for production on-premises connectivity
- [ ] DDoS Protection Standard on Hub VNet (if public-facing)
Related: Azure Well-Architected Framework
Related: Azure Cloud Integration — Service Bus, Event Grid, Hybrid
Related: Security Architect Guide — Zero Trust, mTLS, Private Endpoints
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.