Running out of IP address space in a cloud network is not like running out of disk space. There is no “add more” without a rebuild. Reconfiguring VNet address ranges after workloads are deployed means re-deploying VMs, re-creating private endpoints, and coordinating outages across teams. The right time to plan your network is before the first spoke VNet exists.
Hub-and-spoke is the standard topology for Azure landing zones. Production hub networks carry Azure Firewall with forced tunnel routing, Azure Bastion for secure VM access, and Private DNS Zones for centralized name resolution.
As of March 31, 2026, Azure retired default outbound internet access for VMs in new VNets. New VNets are private-by-default — a VM with no public IP and no explicit egress path has no internet connectivity. A centralized hub with Azure Firewall is now a mandatory requirement for internet egress, not just a security recommendation.
By the end of this guide, you will:
- Identify the mandatory components of a production hub VNet.
- Plan an IP address space that supports three to five years of spoke growth.
- Deploy the hub network using Terraform AVM and Bicep AVM.
- Configure UDRs to route all spoke traffic through Azure Firewall.
- Centralize Private DNS resolution for private endpoints.
This is Post 2 in the Azure Platform Engineering series. It builds on the Connectivity subscription provisioned in Post 1.
Hub-and-Spoke Topology
graph LR
subgraph Hub_VNet [Hub VNet - 10.0.0.0/16]
direction TB
FW[Azure Firewall Subnet]
Bastion[Azure Bastion Subnet]
GW[Gateway Subnet]
DNS[Private DNS Resolver]
end
subgraph Spoke_A [Spoke VNet A - 10.10.0.0/22]
Workload_A[Workload Subnet]
end
subgraph Spoke_B [Spoke VNet B - 10.10.4.0/22]
Workload_B[Workload Subnet]
end
Workload_A -- Peering & UDR --> FW
Workload_B -- Peering & UDR --> FW
FW -- Egress --> Internet((Internet))
GW -- Hybrid --> OnPrem((On-Premises))
style Hub_VNet fill:#e1f5fe,stroke:#01579b
style Spoke_A fill:#f1f8e9,stroke:#33691e
style Spoke_B fill:#f1f8e9,stroke:#33691e
style FW fill:#f44336,color:#fff
Notes:
- UDR (User Defined Routes) on spoke subnets force all
0.0.0.0/0traffic to the Hub’s Firewall private IP. - VNet Peering is non-transitive by default; spokes cannot talk to each other unless routed through the Firewall.
- The Gateway Subnet handles Site-to-Site VPN or ExpressRoute traffic.
The Hub VNet
The hub VNet hosts shared network services that every spoke consumes. Workload teams do not deploy resources here; they peer their spoke VNets to the hub to inherit its security and connectivity.
The hub VNet requires four subnets with names enforced by Azure:
| Subnet Name | Minimum Size | Purpose |
|---|---|---|
AzureFirewallSubnet | /26 | Azure Firewall. |
AzureFirewallManagementSubnet | /26 | Mandatory for forced tunneling and all Basic SKU firewalls. |
AzureBastionSubnet | /26 | Azure Bastion. |
GatewaySubnet | /27 | VPN or ExpressRoute Gateway. |
Scale Tip: Adding a subnet to a VNet with active peerings requires removing and re-creating those peerings. Plan and reserve your hub subnets before establishing any connections.
IP Address Space Planning
Azure VNet address spaces cannot overlap within a peering relationship. Allocate a large block exclusively for Azure and subdivide by purpose.
A Practical Model:
- Hub (East US):
10.0.0.0/16 - Corp Spokes:
10.10.0.0/16–10.49.0.0/16(Workloads needing on-prem access) - Online Spokes:
10.50.0.0/16–10.79.0.0/16(Internet-facing workloads)
Each spoke VNet should receive a /22 (1,024 addresses) by default. This handles most enterprise application requirements without fragmentation.
Azure Firewall and Routing
Forced Tunneling and UDRs
Forced tunneling ensures all internet-bound traffic from spokes flows through Azure Firewall for inspection. This requires a User Defined Route (UDR) on spoke subnets with 0.0.0.0/0 pointing to the Firewall’s private IP.
Avoiding Asymmetric Routing: When an on-premises host connects to a spoke VM, the return traffic might hit the spoke UDR and attempt to exit through the Firewall. The Firewall will drop this traffic because it didn’t see the original inbound request. Fix this by adding a UDR to the hub’s GatewaySubnet that routes traffic destined for the spoke address ranges through the Firewall.
Azure Bastion
Azure Bastion replaces the traditional jump server anti-pattern. It provides RDP and SSH directly through the portal or az CLI without exposing ports 22 or 3389 to the internet.
Use the Standard SKU for hub deployments. Standard Bastion supports peering-based access, allowing it to reach VMs in any peered spoke VNet. The Developer SKU is restricted to its own VNet and is unsuitable for hub-and-spoke designs.
Private DNS Zones
When you use private endpoints, you must resolve the service’s public name (e.g., mystorage.blob.core.windows.net) to its private IP.
Deploy the full set of Private DNS Zones (Blob, Key Vault, SQL, etc.) in the Connectivity subscription from day one. Link these zones to the hub VNet. For spokes to resolve these records, either link the zones to every spoke VNet or deploy an Azure DNS Private Resolver in the hub to forward queries.
Deploying the Hub with Terraform AVM
main.tf
module "hub_vnet" {
source = "Azure/avm-res-network-virtualnetwork/azurerm"
version = "~> 0.7"
name = "vnet-hub-connectivity-001"
address_space = ["10.0.0.0/16"]
subnets = {
AzureFirewallSubnet = { address_prefixes = ["10.0.0.0/26"] }
AzureBastionSubnet = { address_prefixes = ["10.0.1.0/26"] }
}
}
module "firewall" {
source = "Azure/avm-res-network-azurefirewall/azurerm"
version = "~> 0.3"
name = "fw-hub-001"
sku_tier = "Standard"
firewall_policy_id = module.firewall_policy.resource_id
# ... IP configurations
}
Best Practices
- Standard SKUs Only: Use Standard SKU for both Bastion and Firewall. Basic SKUs lack the peering and scaling features required for production hub-and-spoke environments.
- Enable DNS Proxy: Turn on DNS Proxy in the Firewall Policy. This allows spokes to use the Firewall as their DNS server, enabling FQDN-based filtering for all traffic.
- Global Private DNS: Deploy the
Azure/avm-ptn-network-private-link-private-dns-zonesmodule to create all ~60 required DNS zones at once. Reactive zone creation is an operational burden.
Troubleshooting
“Spoke VM cannot reach the internet” Verify the spoke subnet is associated with a UDR. Since March 31, 2026, there is no default outbound access. Check the Firewall logs:
az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "AZFWApplicationRule | where Action == 'Deny' | take 10"
“Private endpoint resolves to a public IP” The Private DNS Zone is likely not linked to the hub VNet, or the spoke VNet is not using the hub for DNS. Confirm the VNet link exists in the Connectivity subscription.
Sources
- CAF Network Topology and Connectivity
- Azure Bastion SKU Comparison
- Azure Verified Modules — Networking
With networking deployed, move to Post 3: Identity and Access Architecture. We will configure the OIDC identities and PIM settings that govern access to this hub.
