Virtual Network Design
What is Azure Virtual Network (VNet)?
Azure Virtual Network (VNet) is the fundamental building block for your private network in Azure. VNets enable:
- Network Isolation: Logically isolated network space in Azure
- Segmentation: Divide networks into subnets for organization and security
- Connectivity: Connect Azure resources, on-premises networks, and the internet
- Traffic Control: Route and filter network traffic
- Service Integration: Private access to Azure PaaS services
How to Design a VNet
Step 1: IP Address Planning
Key Considerations:
- Avoid overlaps with on-premises and other Azure networks
- Plan for growth (allocate larger ranges than current needs)
- Use RFC 1918 private IP ranges
- Document all allocations
Recommended Allocation Strategy:
Corporate IP Space: 10.0.0.0/8
├── Production: 10.0.0.0/12 (10.0.0.0 - 10.15.255.255)
├── Development: 10.16.0.0/12 (10.16.0.0 - 10.31.255.255)
├── Testing: 10.32.0.0/12 (10.32.0.0 - 10.47.255.255)
└── Sandbox: 10.48.0.0/12 (10.48.0.0 - 10.63.255.255)
Region: East US Production
└── Hub VNet: 10.0.0.0/16
└── Spoke 1 (App1): 10.1.0.0/16
└── Spoke 2 (App2): 10.2.0.0/16
Step 2: VNet and Subnet Sizing
VNet Size Recommendations:
| Environment | VNet Size | Usable IPs | Use Case |
|---|---|---|---|
| Large Enterprise Hub | /16 | ~65,000 | Centralized services |
| Application Spoke | /16 | ~65,000 | Multi-tier app |
| Small Workload | /20 | ~4,000 | Single app, few resources |
| Development | /20 | ~4,000 | Dev/test environments |
Subnet Size Recommendations:
| Purpose | Size | Usable IPs | Notes |
|---|---|---|---|
| AzureFirewallSubnet | /26 | 59 | Minimum /26 required |
| GatewaySubnet | /27 | 27 | Minimum /27, /26 recommended |
| AzureBastionSubnet | /26 | 59 | Minimum /26 required |
| AKS Node Pool | /22 - /20 | 1,019 - 4,091 | Depends on pod count |
| Web Tier | /24 | 251 | 10-100 VMs |
| App Tier | /24 | 251 | 10-100 VMs |
| Data Tier | /25 | 123 | 5-50 databases |
| Management | /27 | 27 | Jump boxes, monitoring |
Azure Reserved IPs per Subnet:
.0- Network address.1- Default gateway (Azure).2- Azure DNS mapping.3- Azure reserved.255- Broadcast address (last IP)
Example: Subnet 10.1.1.0/24 has 251 usable IPs (10.1.1.4 - 10.1.1.254)
Step 3: Subnet Design Patterns
Three-Tier Application
VNet: 10.1.0.0/16 (vnet-app-prod-eastus)
├── snet-web: 10.1.1.0/24 (Web/Frontend)
├── snet-app: 10.1.2.0/24 (Application Logic)
├── snet-data: 10.1.3.0/24 (Databases)
├── snet-integration:10.1.4.0/24 (App Service VNet Integration)
└── snet-privatelink:10.1.5.0/24 (Private Endpoints)
Hub VNet Structure
Hub VNet: 10.0.0.0/16 (vnet-hub-prod-eastus)
├── AzureFirewallSubnet: 10.0.0.0/26 (Firewall)
├── GatewaySubnet: 10.0.1.0/27 (VPN/ExpressRoute)
├── AzureBastionSubnet: 10.0.2.0/26 (Bastion)
├── snet-management: 10.0.3.0/24 (Jump boxes)
└── snet-shared-services: 10.0.4.0/24 (DNS, monitoring)
Step 4: VNet Peering Design
Hub-Spoke Topology (Recommended):
Hub VNet (10.0.0.0/16)
/ | \
/ | \
/ | \
Spoke 1 Spoke 2 Spoke 3
(10.1.0.0/16)(10.2.0.0/16)(10.3.0.0/16)
Peering Options:
AllowVirtualNetworkAccess: Enable communicationAllowForwardedTraffic: Allow traffic from other networksAllowGatewayTransit: Share VPN/ExpressRoute (Hub only)UseRemoteGateways: Use hub's gateway (Spokes only)
No Transitive Routing: Spokes cannot communicate directly. Traffic must route through hub (Azure Firewall or NVA).
Terraform Example
# Variables for IP planning
variable "hub_vnet_cidr" {
default = "10.0.0.0/16"
}
variable "spoke_prod_cidr" {
default = "10.1.0.0/16"
}
# Hub VNet
resource "azurerm_virtual_network" "hub" {
name = "vnet-hub-prod-${var.region}"
resource_group_name = azurerm_resource_group.hub.name
location = var.region
address_space = [var.hub_vnet_cidr]
dns_servers = ["10.0.4.4", "10.0.4.5"] # Custom DNS if needed
tags = {
Environment = "Production"
Purpose = "Hub"
CostCenter = "IT-Network"
}
}
# Hub Subnets
resource "azurerm_subnet" "firewall" {
name = "AzureFirewallSubnet" # Name is mandatory
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.0.0/26"]
}
resource "azurerm_subnet" "gateway" {
name = "GatewaySubnet" # Name is mandatory
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.1.0/27"]
}
resource "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet" # Name is mandatory
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.2.0/26"]
}
# Spoke VNet
resource "azurerm_virtual_network" "spoke_prod" {
name = "vnet-spoke-prod-${var.region}"
resource_group_name = azurerm_resource_group.spoke.name
location = var.region
address_space = [var.spoke_prod_cidr]
tags = {
Environment = "Production"
Purpose = "Application Workloads"
Application = "MyApp"
}
}
# Application Tier Subnets
locals {
spoke_subnets = {
web = {
name = "snet-web"
prefix = "10.1.1.0/24"
}
app = {
name = "snet-app"
prefix = "10.1.2.0/24"
}
data = {
name = "snet-data"
prefix = "10.1.3.0/24"
# Disable service endpoints for data tier
service_endpoints = ["Microsoft.Sql", "Microsoft.Storage"]
}
}
}
resource "azurerm_subnet" "spoke" {
for_each = local.spoke_subnets
name = each.value.name
resource_group_name = azurerm_resource_group.spoke.name
virtual_network_name = azurerm_virtual_network.spoke_prod.name
address_prefixes = [each.value.prefix]
service_endpoints = lookup(each.value, "service_endpoints", [])
}
# VNet Peering: Hub to Spoke
resource "azurerm_virtual_network_peering" "hub_to_spoke_prod" {
name = "peer-hub-to-spoke-prod"
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
remote_virtual_network_id = azurerm_virtual_network.spoke_prod.id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = true # Hub shares gateway
use_remote_gateways = false
}
# VNet Peering: Spoke to Hub
resource "azurerm_virtual_network_peering" "spoke_prod_to_hub" {
name = "peer-spoke-prod-to-hub"
resource_group_name = azurerm_resource_group.spoke.name
virtual_network_name = azurerm_virtual_network.spoke_prod.name
remote_virtual_network_id = azurerm_virtual_network.hub.id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = false # Set true after gateway exists
depends_on = [
azurerm_virtual_network_peering.hub_to_spoke_prod
]
}
# User Defined Route (Force traffic through Firewall)
resource "azurerm_route_table" "spoke" {
name = "rt-spoke-prod"
resource_group_name = azurerm_resource_group.spoke.name
location = var.region
disable_bgp_route_propagation = false
route {
name = "to-internet-via-firewall"
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = azurerm_firewall.hub.ip_configuration[0].private_ip_address
}
route {
name = "to-onprem-via-firewall"
address_prefix = "192.168.0.0/16" # On-prem CIDR
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = azurerm_firewall.hub.ip_configuration[0].private_ip_address
}
}
# Associate Route Table to Subnets
resource "azurerm_subnet_route_table_association" "spoke" {
for_each = azurerm_subnet.spoke
subnet_id = each.value.id
route_table_id = azurerm_route_table.spoke.id
}
Best Practices
IP Address Planning
✅ Do:
- Use /16 for VNets to allow for growth
- Document IP allocations in a central spreadsheet
- Reserve IP ranges for future regions and environments
- Use consistent CIDR allocation across environments
- Test for overlaps before deployment
❌ Avoid:
- Overlapping IP ranges with on-premises
- Using public IP ranges for private networks
- Allocating too small subnets that can't grow
- Using 192.168.x.x if it conflicts with home networks (VPN users)
Subnet Design
✅ Do:
- Create separate subnets for each tier (web, app, data)
- Use /24 or larger for application subnets
- Plan for at least 2x current capacity
- Use descriptive naming (snet-web, snet-app, snet-data)
- Apply NSGs to every subnet
❌ Avoid:
- Putting all resources in one subnet
- Using /29 or smaller (too few IPs, only 3 usable)
- Mixing security zones in same subnet
- Creating too many tiny subnets (management overhead)
VNet Peering
✅ Do:
- Use hub-spoke for enterprise deployments
- Enable forwarded traffic in hub
- Document peering relationships
- Use resource locks on peering connections
❌ Avoid:
- Creating spoke-to-spoke peerings (use hub routing)
- Full mesh peering (doesn't scale, use Virtual WAN)
- Forgetting bidirectional peering requirements
- Deleting peerings without impact analysis
Routing
✅ Do:
- Force tunnel traffic through Azure Firewall in hub
- Use User Defined Routes (UDR) for traffic control
- Document routing decisions
- Test failover scenarios
❌ Avoid:
- Allowing direct internet access from data tier
- Creating routing loops
- Bypassing security controls with 0.0.0.0/0 to Internet
Things to Avoid
❌ Don't change VNet address space after deployment (breaks resources) ❌ Don't create VNets without planning for hybrid connectivity ❌ Don't use overlapping CIDRs across environments ❌ Don't deploy production without route tables ❌ Don't mix environments (prod/dev) in same VNet ❌ Don't create more than 500 peerings per VNet ❌ Don't use default names (vnet1, subnet1) ❌ Don't skip DNS planning (use Private DNS Zones) ❌ Don't forget to enable DDoS Protection on production VNets
CIDR Calculation Reference
| CIDR | Subnet Mask | Total IPs | Usable IPs (Azure) | Use Case |
|---|---|---|---|---|
| /16 | 255.255.0.0 | 65,536 | ~65,000 | Large VNet |
| /20 | 255.255.240.0 | 4,096 | ~4,000 | Medium VNet |
| /22 | 255.255.252.0 | 1,024 | ~1,000 | AKS nodes |
| /24 | 255.255.255.0 | 256 | 251 | Standard subnet |
| /25 | 255.255.255.128 | 128 | 123 | Small subnet |
| /26 | 255.255.255.192 | 64 | 59 | Firewall/Bastion |
| /27 | 255.255.255.224 | 32 | 27 | Gateway subnet |
| /28 | 255.255.255.240 | 16 | 11 | Very small |
| /29 | 255.255.255.248 | 8 | 3 | Not recommended |
Advanced Scenarios
Multi-Region Hub-Spoke
East US Hub (10.0.0.0/16) <--Global VNet Peering--> West US Hub (10.10.0.0/16)
| |
+-- Spoke 1 (10.1.0.0/16) +-- Spoke 4 (10.11.0.0/16)
+-- Spoke 2 (10.2.0.0/16) +-- Spoke 5 (10.12.0.0/16)
+-- Spoke 3 (10.3.0.0/16)
Global VNet Peering Considerations:
- Cross-region bandwidth charges apply
- Latency depends on region distance
- Use for disaster recovery and geo-distribution
Azure Virtual WAN (For Very Large Deployments)
Consider Virtual WAN when:
- 50+ branch locations
- Multiple Azure regions
- Need managed routing
- SD-WAN integration
Monitoring & Troubleshooting
Verify VNet Configuration
# List all VNets
az network vnet list -o table
# Show VNet details
az network vnet show \
--resource-group rg-network \
--name vnet-hub-prod-eastus
# List subnets
az network vnet subnet list \
--resource-group rg-network \
--vnet-name vnet-hub-prod-eastus \
-o table
# Check peering status
az network vnet peering list \
--resource-group rg-network \
--vnet-name vnet-hub-prod-eastus \
-o table
Check Effective Routes
# See routes for a specific NIC
az network nic show-effective-route-table \
--resource-group rg-app \
--name nic-vm-web-01 \
-o table