Skip to main content

Networking Overview

Every enterprise Azure deployment is built on top of networking decisions made early — address space, topology, DNS, how traffic flows between on-premises and cloud. I've seen these decisions made carefully and I've inherited environments where they weren't. Fixing address space overlap mid-project is a multi-week exercise that touches every team. Getting it right upfront costs an afternoon.

Key Services

ServicePurposeUse Case
Virtual Networks (VNet)Network isolation and segmentationFoundation for all Azure resources
SubnetsVNet subdivision for security/organizationSeparate web, app, data tiers
Azure FirewallManaged network securityCentralized protection for hub-spoke
Network Security Groups (NSG)Subnet/NIC-level firewall rulesMicro-segmentation
Load BalancerLayer 4 load balancingInternal/external load distribution
Application GatewayLayer 7 load balancing with WAFWeb application delivery
VPN GatewaySite-to-Site and Point-to-Site VPNHybrid connectivity (IPsec)
ExpressRoutePrivate dedicated connectionHigh-bandwidth hybrid connectivity
Azure BastionSecure RDP/SSH accessJump box replacement
Private LinkPrivate access to PaaS servicesSecure SaaS/PaaS consumption

Network Architecture Patterns

                   On-Premises
|
[VPN Gateway]
|
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)
[Prod App] [Dev/Test] [Shared Services]

Benefits:

  • Centralized security (Azure Firewall in hub)
  • Shared services (DNS, monitoring, bastion)
  • Network isolation between spokes
  • Cost optimization (single VPN/ExpressRoute)
  • Simplified management

Hub VNet Components:

  • Azure Firewall subnet (/26)
  • Gateway subnet (/27) for VPN/ExpressRoute
  • Azure Bastion subnet (/26)
  • Management subnet for jump boxes

Spoke VNet Pattern:

  • Application tier subnet
  • Data tier subnet
  • Integration subnet for VNet integration

Flat Network (Simple Workloads)

Single VNet with multiple subnets:

VNet (10.0.0.0/16)
├── Web Subnet (10.0.1.0/24)
├── App Subnet (10.0.2.0/24)
└── Data Subnet (10.0.3.0/24)

Use for:

  • Small deployments
  • Development/testing environments
  • Single application workloads

IP Address Planning

CIDR Allocation Best Practices

Azure Reserved Addresses (per subnet):

  • First 3 IPs: Azure reserved (network, gateway, Azure DNS)
  • Last IP: Broadcast address
  • Example: In 10.0.1.0/24, 10.0.1.0-10.0.1.3 and 10.0.1.255 are reserved

Recommended Sizes:

Hub VNet:              10.0.0.0/16  (65,536 IPs)
Azure Firewall: 10.0.0.0/26 (64 IPs)
Gateway Subnet: 10.0.1.0/27 (32 IPs)
Bastion: 10.0.2.0/26 (64 IPs)
Management: 10.0.3.0/24 (256 IPs)

Spoke VNet (Prod): 10.1.0.0/16
Web Tier: 10.1.1.0/24 (256 IPs)
App Tier: 10.1.2.0/24 (256 IPs)
Data Tier: 10.1.3.0/24 (256 IPs)
AKS: 10.1.4.0/22 (1,024 IPs for pods)

Private IP Ranges (RFC 1918):

  • 10.0.0.0/8 (10.0.0.0 - 10.255.255.255) - Recommended for Azure
  • 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
  • 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)

Avoid: Overlapping with on-premises networks ✅ Do: Document IP allocation in central registry

Connectivity Options

VPN Gateway (Site-to-Site)

  • Bandwidth: Up to 10 Gbps (VpnGw5AZ SKU)
  • Use Case: Encrypted hybrid connectivity
  • Cost: Lower than ExpressRoute
  • Latency: Internet-dependent (higher variance)

ExpressRoute

  • Bandwidth: 50 Mbps to 100 Gbps
  • Use Case: Mission-critical, high-bandwidth workloads
  • Cost: Higher (monthly + provider fees)
  • Latency: Predictable, low latency (private connection)
  • SLA: 99.95%

Azure Bastion

  • Use Case: Secure RDP/SSH without public IPs
  • Benefits: No jump box maintenance, integrated with NSGs
  • Cost: Hourly + data transfer

Network Security Layers

  1. Azure Firewall (Hub): Centralized protection, FQDN filtering, threat intelligence
  2. NSGs (Subnet/NIC): Stateful firewall, allow/deny rules
  3. Application Security Groups (ASG): Group VMs logically for NSG rules
  4. WAF (Application Gateway/Front Door): OWASP Top 10 protection
  5. DDoS Protection: Standard or Premium tier
  6. Private Link: Eliminate public endpoints for PaaS

Quick Start

Create Hub-Spoke Network

# Create Hub VNet
az network vnet create \
--resource-group rg-network-hub \
--name vnet-hub-prod-eastus \
--address-prefix 10.0.0.0/16 \
--subnet-name AzureFirewallSubnet \
--subnet-prefix 10.0.0.0/26

# Create Gateway Subnet
az network vnet subnet create \
--resource-group rg-network-hub \
--vnet-name vnet-hub-prod-eastus \
--name GatewaySubnet \
--address-prefix 10.0.1.0/27

# Create Spoke VNet
az network vnet create \
--resource-group rg-network-spoke-prod \
--name vnet-spoke-prod-eastus \
--address-prefix 10.1.0.0/16 \
--subnet-name snet-web \
--subnet-prefix 10.1.1.0/24

# Create VNet Peering (Hub to Spoke)
az network vnet peering create \
--resource-group rg-network-hub \
--name hub-to-spoke-prod \
--vnet-name vnet-hub-prod-eastus \
--remote-vnet vnet-spoke-prod-eastus \
--allow-vnet-access \
--allow-forwarded-traffic \
--allow-gateway-transit

# Create VNet Peering (Spoke to Hub)
az network vnet peering create \
--resource-group rg-network-spoke-prod \
--name spoke-prod-to-hub \
--vnet-name vnet-spoke-prod-eastus \
--remote-vnet vnet-hub-prod-eastus \
--allow-vnet-access \
--allow-forwarded-traffic \
--use-remote-gateways

Terraform Example

# Hub VNet
resource "azurerm_virtual_network" "hub" {
name = "vnet-hub-prod-eastus"
resource_group_name = azurerm_resource_group.hub.name
location = azurerm_resource_group.hub.location
address_space = ["10.0.0.0/16"]

tags = {
Environment = "Production"
Purpose = "Hub Network"
}
}

# Azure Firewall Subnet
resource "azurerm_subnet" "firewall" {
name = "AzureFirewallSubnet"
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.0.0/26"]
}

# Gateway Subnet
resource "azurerm_subnet" "gateway" {
name = "GatewaySubnet"
resource_group_name = azurerm_resource_group.hub.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.1.0/27"]
}

# Spoke VNet
resource "azurerm_virtual_network" "spoke_prod" {
name = "vnet-spoke-prod-eastus"
resource_group_name = azurerm_resource_group.spoke_prod.name
location = azurerm_resource_group.spoke_prod.location
address_space = ["10.1.0.0/16"]

tags = {
Environment = "Production"
Purpose = "Application Workloads"
}
}

# Spoke Subnets
resource "azurerm_subnet" "web" {
name = "snet-web"
resource_group_name = azurerm_resource_group.spoke_prod.name
virtual_network_name = azurerm_virtual_network.spoke_prod.name
address_prefixes = ["10.1.1.0/24"]
}

resource "azurerm_subnet" "app" {
name = "snet-app"
resource_group_name = azurerm_resource_group.spoke_prod.name
virtual_network_name = azurerm_virtual_network.spoke_prod.name
address_prefixes = ["10.1.2.0/24"]
}

# VNet Peering: Hub to Spoke
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
name = "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
}

# VNet Peering: Spoke to Hub
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "spoke-prod-to-hub"
resource_group_name = azurerm_resource_group.spoke_prod.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
use_remote_gateways = false # Set true after gateway deployment
}

# Network Security Group
resource "azurerm_network_security_group" "web" {
name = "nsg-web"
resource_group_name = azurerm_resource_group.spoke_prod.name
location = azurerm_resource_group.spoke_prod.location

security_rule {
name = "Allow-HTTPS"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
}
}

# Associate NSG to Subnet
resource "azurerm_subnet_network_security_group_association" "web" {
subnet_id = azurerm_subnet.web.id
network_security_group_id = azurerm_network_security_group.web.id
}

Best Practices

IP address space is the one networking decision that's genuinely difficult to change after deployment. I plan it deliberately at the start of every engagement.

Use Hub-and-Spoke for Enterprise

  • Centralize shared services
  • Isolate workloads between spokes
  • Implement forced tunneling through Azure Firewall
  • Use Azure Firewall for east-west and north-south traffic

Implement Network Segmentation

  • Separate tiers (web, app, data) into different subnets
  • Apply NSGs to every subnet
  • Use Application Security Groups for logical grouping
  • Deny all inbound by default, allow specific rules

Plan for Growth

  • Allocate larger CIDR blocks than currently needed
  • Reserve IP space for future regions
  • Document all IP allocations
  • Avoid /29 or smaller subnets (limited IPs)

Secure Your Network

  • I use Private Link for PaaS services (Storage, SQL, Key Vault)
  • Deploy Azure Bastion instead of jump boxes
  • Enable DDoS Protection Standard for production
  • Implement WAF for web applications
  • Never expose databases directly to internet

Things to Avoid

Don't create VNets without planning IP address scheme ❌ Don't use overlapping IP ranges with on-premises ❌ Don't deploy resources without NSGs ❌ Don't allow unrestricted RDP/SSH from internet (0.0.0.0/0) ❌ Don't use default route (0.0.0.0/0) to internet from data tier ❌ Don't peer spokes directly (use hub for routing) ❌ Don't use subnet masks smaller than /29 (too few IPs) ❌ Don't hardcode IP addresses in application code ❌ Don't expose PaaS services with public endpoints when Private Link available

Do use hub-spoke topology for multiple workloads ✅ Do implement defense-in-depth with multiple security layers ✅ Do use Azure Firewall or NVA for centralized control ✅ Do enable NSG flow logs for security analysis ✅ Do use Private DNS Zones for name resolution ✅ Do implement User Defined Routes (UDR) for traffic control ✅ Do test failover scenarios for hybrid connectivity ✅ Do monitor network health with Network Watcher

CI/CD Integration

Validate Network Configuration in Pipeline

name: Network Validation

on: [pull_request]

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Validate IP CIDR Ranges
run: |
# Check for overlapping ranges
python scripts/validate_cidr.py

- name: Check NSG Rules
run: |
# Ensure no 0.0.0.0/0 inbound rules on port 22/3389
terraform show -json tfplan | \
jq '.planned_values.root_module.resources[] | select(.type=="azurerm_network_security_rule") | select(.values.source_address_prefix=="*" or .values.source_address_prefix=="0.0.0.0/0") | select(.values.destination_port_range=="22" or .values.destination_port_range=="3389")'

Monitoring & Troubleshooting

Enable Network Watcher

az network watcher configure \
--resource-group NetworkWatcherRG \
--locations eastus westus \
--enabled true

NSG Flow Logs

az network watcher flow-log create \
--resource-group rg-network \
--name nsg-flow-log-web \
--nsg nsg-web \
--storage-account stlogs \
--enabled true \
--retention 30

Connection Troubleshoot

az network watcher test-connectivity \
--resource-group rg-app \
--source-resource vm-web-01 \
--dest-address 10.1.2.5 \
--dest-port 1433