Skip to main content

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:

  1. Avoid overlaps with on-premises and other Azure networks
  2. Plan for growth (allocate larger ranges than current needs)
  3. Use RFC 1918 private IP ranges
  4. 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:

EnvironmentVNet SizeUsable IPsUse Case
Large Enterprise Hub/16~65,000Centralized services
Application Spoke/16~65,000Multi-tier app
Small Workload/20~4,000Single app, few resources
Development/20~4,000Dev/test environments

Subnet Size Recommendations:

PurposeSizeUsable IPsNotes
AzureFirewallSubnet/2659Minimum /26 required
GatewaySubnet/2727Minimum /27, /26 recommended
AzureBastionSubnet/2659Minimum /26 required
AKS Node Pool/22 - /201,019 - 4,091Depends on pod count
Web Tier/2425110-100 VMs
App Tier/2425110-100 VMs
Data Tier/251235-50 databases
Management/2727Jump 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 communication
  • AllowForwardedTraffic: Allow traffic from other networks
  • AllowGatewayTransit: 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

CIDRSubnet MaskTotal IPsUsable IPs (Azure)Use Case
/16255.255.0.065,536~65,000Large VNet
/20255.255.240.04,096~4,000Medium VNet
/22255.255.252.01,024~1,000AKS nodes
/24255.255.255.0256251Standard subnet
/25255.255.255.128128123Small subnet
/26255.255.255.1926459Firewall/Bastion
/27255.255.255.2243227Gateway subnet
/28255.255.255.2401611Very small
/29255.255.255.24883Not 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