Azure: create a Virtual Network with subnets & NSG
Azure Virtual Networks (VNets) are fundamental to deploying isolated and secure cloud resources. They enable logical isolation of resources, control over…
Azure Virtual Networks (VNets) are fundamental to deploying isolated and secure cloud resources. They enable logical isolation of resources, control over traffic flow, and connectivity to on-premises networks. This guide details the creation of a VNet with multiple subnets and associated Network Security Groups (NSGs) using the Azure CLI, focusing on practical implementation for production environments.
Understanding Azure Virtual Networks
A VNet serves as your private network in the Azure cloud. It's a logical isolation of the Azure cloud dedicated to your subscription. Within a VNet, you can define one or more address spaces, which are further divided into subnets. Resources deployed into these subnets can communicate with each other, with on-premises networks via VPN gateways or ExpressRoute, and with the internet.
Key VNet Components:
- Address Space: The CIDR block(s) defining the IP range for the entire VNet (e.g.,
10.0.0.0/16). - Subnets: Logical subdivisions of the VNet's address space. Resources are deployed into specific subnets. Each subnet must have a unique CIDR block within the VNet's address space.
- Network Security Groups (NSGs): Act as virtual firewalls for subnets or individual network interfaces. They contain security rules that allow or deny inbound or outbound network traffic based on source/destination IP address, port, and protocol.
- Route Tables: Custom routing rules that override Azure's default routing. Useful for forced tunneling or virtual appliance integration.
- Service Endpoints/Private Endpoints: Provide secure, direct connectivity to Azure PaaS services, bypassing the public internet.
Designing Your VNet and Subnets
Effective VNet design starts with careful planning of IP addressing and subnet segmentation. Consider the number of resources, their communication patterns, and security boundaries. For production, it's common to segment into at least application, database, and management subnets.
Example Production Design:
- VNet:
vnet-prod(10.0.0.0/16) - Subnets:
snet-web: For web servers/load balancers (10.0.1.0/24)snet-app: For application servers (10.0.2.0/24)snet-db: For database servers (10.0.3.0/24)snet-mgmt: For jump boxes/management tools (10.0.254.0/24)
- NSGs:
nsg-web: Applied tosnet-webnsg-app: Applied tosnet-appnsg-db: Applied tosnet-dbnsg-mgmt: Applied tosnet-mgmt
CIDR Sizing Considerations:
- Azure reserves the first four and the last IP address in every subnet for internal use. For example, a
/24subnet (256 addresses) provides 251 usable IPs. - Allocate enough IPs for current needs and future growth. A
/24is often sufficient for smaller subnets; larger deployments might require/23or/22. - Avoid overlapping IP ranges between VNets that need to communicate (e.g., via VNet peering or VPN Gateway).
Creating the Virtual Network and Initial Subnet
We'll start by creating a resource group and the VNet with its primary address space and one subnet. The example uses rg-prod as the resource group and westeurope as the location.
# Set environment variables for consistency
RG_NAME="rg-prod"
LOCATION="westeurope"
VNET_NAME="vnet-prod"
VNET_ADDRESS_PREFIX="10.0.0.0/16"
SUBNET_WEB_NAME="snet-web"
SUBNET_WEB_PREFIX="10.0.1.0/24"
# Create a resource group if it doesn't exist
az group create --name $RG_NAME --location $LOCATION
# Create the VNet and the first subnet (snet-web)
az network vnet create \
--resource-group $RG_NAME \
--name $VNET_NAME \
--address-prefix $VNET_ADDRESS_PREFIX \
--subnet-name $SUBNET_WEB_NAME \
--subnet-prefix $SUBNET_WEB_PREFIX \
--location $LOCATION
The command above creates vnet-prod with a 10.0.0.0/16 address space and includes the first subnet, snet-web, defined by 10.0.1.0/24. Specifying --location on the VNet creation ensures all associated network resources reside in that region.
Adding Additional Subnets
After the VNet is created, you can add more subnets using the az network vnet subnet create command. Each subnet must have a unique CIDR block that falls within the VNet's address space and does not overlap with existing subnets.
# Define additional subnet parameters
SUBNET_APP_NAME="snet-app"
SUBNET_APP_PREFIX="10.0.2.0/24"
SUBNET_DB_NAME="snet-db"
SUBNET_DB_PREFIX="10.0.3.0/24"
SUBNET_MGMT_NAME="snet-mgmt"
SUBNET_MGMT_PREFIX="10.0.254.0/24" # Placed at the end for logical separation
# Add the 'app' subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_APP_NAME \
--address-prefixes $SUBNET_APP_PREFIX
# Add the 'db' subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_DB_NAME \
--address-prefixes $SUBNET_DB_PREFIX
# Add the 'management' subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_MGMT_NAME \
--address-prefixes $SUBNET_MGMT_PREFIX
Verify the subnets are created:
az network vnet show --resource-group $RG_NAME --name $VNET_NAME --query subnets[].{Name:name,AddressPrefix:addressPrefix} -o table
Creating and Associating Network Security Groups (NSGs)
NSGs are crucial for controlling traffic flow to and from your subnets. Best practice dictates applying NSGs to subnets rather than individual NICs, simplifying management and ensuring consistent policy for all resources within that subnet.
Creating NSGs
# Create NSG for web subnet
az network nsg create --resource-group $RG_NAME --name nsg-web
# Create NSG for app subnet
az network nsg create --resource-group $RG_NAME --name nsg-app
# Create NSG for db subnet
az network nsg create --resource-group $RG_NAME --name nsg-db
# Create NSG for management subnet
az network nsg create --resource-group $RG_NAME --name nsg-mgmt
Associating NSGs with Subnets
Once NSGs are created, they need to be linked to their respective subnets. This is done using the az network vnet subnet update command.
# Associate nsg-web with snet-web
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_WEB_NAME \
--network-security-group nsg-web
# Associate nsg-app with snet-app
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_APP_NAME \
--network-security-group nsg-app
# Associate nsg-db with snet-db
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_DB_NAME \
--network-security-group nsg-db
# Associate nsg-mgmt with snet-mgmt
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name $VNET_NAME \
--name $SUBNET_MGMT_NAME \
--network-security-group nsg-mgmt
Configuring NSG Rules
NSG rules define the actual traffic policies. Each rule has a priority (100-4096), direction (Inbound/Outbound), access (Allow/Deny), protocol, source/destination IP, and source/destination port ranges. Lower priority numbers are processed first.
Example NSG Rule Configuration (nsg-web)
For nsg-web, we might allow HTTP/HTTPS from the internet and SSH from the management subnet.
# Allow inbound HTTP (Port 80) from any source
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name "AllowHTTP" \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes "*" \
--source-port-ranges "*" \
--destination-address-prefixes $SUBNET_WEB_PREFIX \
--destination-port-ranges "80"
# Allow inbound HTTPS (Port 443) from any source
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name "AllowHTTPS" \
--priority 110 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes "*" \
--source-port-ranges "*" \
--destination-address-prefixes $SUBNET_WEB_PREFIX \
--destination-port-ranges "443"
# Allow inbound SSH (Port 22) from management subnet only
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--name "AllowSSH_from_MGMT" \
--priority 120 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes $SUBNET_MGMT_PREFIX \
--source-port-ranges "*" \
--destination-address-prefixes $SUBNET_WEB_PREFIX \
--destination-port-ranges "22"
Example NSG Rule Configuration (nsg-app)
For nsg-app, allow inbound traffic from the web subnet (port 8080 for app traffic) and SSH from the management subnet.
# Allow inbound application traffic (Port 8080) from web subnet
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-app \
--name "AllowAppTraffic_from_WEB" \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes $SUBNET_WEB_PREFIX \
--source-port-ranges "*" \
--destination-address-prefixes $SUBNET_APP_PREFIX \
--destination-port-ranges "8080"
# Allow inbound SSH (Port 22) from management subnet only
az network nsg rule create \
--resource-group $RG_NAME \
--nsg-name nsg-app \
--name "AllowSSH_from_MGMT" \
--priority 110 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes $SUBNET_MGMT_PREFIX \
--source-port-ranges "*" \
--destination-address-prefixes $SUBNET_APP_PREFIX \
--destination-port-ranges "22"
Repeat similar rule creation for nsg-db (e.g., allow traffic from snet-app on database ports like 3306 for MySQL, 1433 for SQL Server) and nsg-mgmt (e.g., allow RDP/SSH from trusted external IPs).
Common Service Tags for NSG Rules:
| Service Tag | Description |
|---|---|
Internet |
IP address ranges of public IP addresses on the internet. |
VirtualNetwork |
All IP address ranges within the VNet and peered VNets. |
AzureLoadBalancer |
Azure's infrastructure load balancer. |
Storage |
Azure Storage public IP address range. |
Sql |
Azure SQL Database public IP address range. |
Using service tags simplifies rule management by abstracting dynamic IP ranges of Azure services.
Verifying Your Configuration
Always verify your network configuration after making changes to ensure everything is set up as intended and to troubleshoot any issues.
# Show VNet details including subnets and associated NSGs
az network vnet show \
--resource-group $RG_NAME \
--name $VNET_NAME \
--query '{Name:name, AddressSpace:addressSpace, Subnets:subnets[].{Name:name, AddressPrefix:addressPrefix, NSG:networkSecurityGroup.name}}' \
-o jsonc
# Show NSG rules for a specific NSG
az network nsg rule list \
--resource-group $RG_NAME \
--nsg-name nsg-web \
--query '[].{Name:name, Priority:priority, Direction:direction, Access:access, Protocol:protocol, Source:sourceAddressPrefix, Destination:destinationAddressPrefix, DPorts:destinationPortRanges}' \
-o table
Common Pitfalls
- Overlapping IP Ranges: This is a common error when creating subnets or peering VNets. Azure will prevent VNet creation with overlapping address spaces, but it's essential to plan carefully.
- Insufficient IP Addresses: Not allocating enough IP addresses in a subnet can lead to resource deployment failures. Factor in Azure's reserved IPs and future growth.
- Incorrect NSG Rule Priority: Rules are processed by priority (lower number first). A higher priority "Deny" rule might inadvertently block traffic meant to be allowed by a lower priority rule.
- Missing Default Deny Rules: Azure NSGs have implicit deny rules at the lowest priority. If you don't explicitly allow traffic, it will be denied. Sometimes, admins forget to allow necessary outbound traffic.
- NSG Applied to Wrong Scope: Applying an NSG to a NIC when it should be on a subnet (or vice-versa) can lead to unexpected traffic flow. Subnet-level NSGs are generally preferred for consistency.
- Public IP Exposure: Accidentally allowing inbound traffic from "Any" (
*) to sensitive ports (like RDP/SSH) on internet-facing resources. Always restrict source IPs for management access.