Virtual networks & NSGs

Subnets, NSGs/ASGs, peering, gateways.

Intermediate35 min · lesson 10 of 15

Your company leases an office building. The whole building has one street address, and that is the address space. Each floor holds one department, and those are subnets. A keycard reader on every stairwell door decides who gets onto a floor, and those are network security groups. The badges themselves say *engineering* or *finance* instead of listing names, so people can change desks without anyone reprinting the door rules, and those are application security groups. Azure networking is that building, almost line for line. A virtual network (VNet) is your own walled-off slice of Azure's network inside one region, and you choose the private IP (Internet Protocol) address range it runs on. Nearly everything you deploy lives on one of its floors: virtual machines, AKS (Azure Kubernetes Service) nodes, App Service integrations. In this lesson you build a tiered VNet from the az command line, lock it down, prove the rules actually work, then wire it to a second building.

Draw the floor plan: address space and subnets

A VNet lives in exactly one region and one subscription. Its size comes from a CIDR block (Classless Inter-Domain Routing, the 10.20.0.0/16 style of notation). The number after the slash says how many bits at the front are locked as the network's identity, so with /16 the first 16 bits are fixed and everything below them is yours: 65,536 addresses to hand out. You then cut that space into subnets, smaller CIDR blocks that keep workloads apart by tier or by how much you trust them. Web servers on one floor, application servers on another, databases on a third. Two planning habits save you real pain later. First, never let a range overlap with your on-premises network or with another VNet, because overlapping networks can never be peered or joined by a VPN (virtual private network) tunnel, and the only cure is renumbering. Second, subnets are smaller than they look. Azure quietly reserves five addresses in every subnet (the network address, the default gateway at .1, two for Azure DNS (Domain Name System), and the broadcast address), so a /24 gives you 251 usable IPs rather than 256, and the smallest subnet Azure allows is a /29, which leaves you three. That "sized for 256, full at 251" surprise is an AZ-104 exam favorite and a real capacity-planning trap.

01-build-vnet.azcli
# Resource group + VNet with three tiered subnets
az group create -n net-rg -l eastus2 -o none
az network vnet create -g net-rg -n prod-vnet \
--address-prefixes 10.20.0.0/16 \
--subnet-name snet-web --subnet-prefixes 10.20.1.0/24 -o none
az network vnet subnet create -g net-rg --vnet-name prod-vnet \
-n snet-app --address-prefixes 10.20.2.0/24 -o none
az network vnet subnet create -g net-rg --vnet-name prod-vnet \
-n snet-data --address-prefixes 10.20.3.0/24 -o none
az network vnet subnet list -g net-rg --vnet-name prod-vnet -o table
# Name AddressPrefix ProvisioningState (columns trimmed)
# --------- -------------- -----------------
# snet-web 10.20.1.0/24 Succeeded
# snet-app 10.20.2.0/24 Succeeded
# snet-data 10.20.3.0/24 Succeeded

Network security groups: a guest list read in number order

A network security group (NSG) is a guest list. You write allow and deny rules, then hang that list on a subnet, on one machine's NIC (network interface card, the virtual network adapter attached to a VM), or on both. Every rule carries a priority number between 100 and 4096. Azure reads the list from the lowest number upward, acts on the *first* rule that matches, and stops reading right there. Nothing further down the list can undo that decision. Every NSG also ships with built-in default rules numbered 65000 and above. You can override them with a lower number, but you can never delete them: AllowVnetInBound (anything tagged VirtualNetwork, which covers this VNet plus peered and gateway-connected networks), AllowAzureLoadBalancerInBound (health checks from Azure's load balancer), and a final catch-all DenyAllInBound. NSGs are stateful, meaning Azure remembers a flow it let in and allows the reply back out on its own, so you never write a mirror rule for return traffic. Rules can also point at service tags, Microsoft-maintained labels such as Internet, VirtualNetwork or Storage that stand in for a constantly updated list of IP ranges, which spares you from hand-curating address lists that go stale the week after you write them.

The explicit deny-all-in at priority 4096 in the next block looks like a duplicate of the built-in DenyAllInBound. It isn't. It fires *before* AllowVnetInBound at 65000, so a compromised machine sitting somewhere else in the same VNet can no longer walk sideways into the web tier. There is a price for that. The same rule also fires before AllowAzureLoadBalancerInBound at 65001. Put this subnet behind a load balancer one day and the health probes get dropped with no error logged anywhere, the balancer marks every server unhealthy, and the backend pool empties out. Before that day comes, add an allow rule for the AzureLoadBalancer service tag.

02-web-nsg.azcli
# Web tier NSG: HTTPS in from the internet, explicit deny for everything else
az network nsg create -g net-rg -n web-nsg -o none
az network nsg rule create -g net-rg --nsg-name web-nsg -n allow-https \
--priority 100 --direction Inbound --access Allow --protocol Tcp \
--source-address-prefixes Internet --destination-port-ranges 443 -o none
az network nsg rule create -g net-rg --nsg-name web-nsg -n deny-all-in \
--priority 4096 --direction Inbound --access Deny --protocol '*' \
--source-address-prefixes '*' --destination-port-ranges '*' -o none
# Attach to the subnet — every NIC in snet-web now inherits these rules
az network vnet subnet update -g net-rg --vnet-name prod-vnet \
-n snet-web --network-security-group web-nsg -o none
az network nsg rule list -g net-rg --nsg-name web-nsg -o table
# Name Priority Direction Access Protocol DestinationPortRanges (columns trimmed)
# allow-https 100 Inbound Allow Tcp 443
# deny-all-in 4096 Inbound Deny * *

Two layers of NSG can apply to a single packet. Coming in, Azure checks the subnet's NSG first and the NIC's second, and both have to allow the packet or it dies. Going out, the order flips: NIC first, subnet second. Most teams standardize on subnet-level NSGs and keep NIC-level ones for genuine exceptions. Two overlapping rule sets are twice as hard to unpick at 2 a.m.

Choosing a connectivity option
Two networks to connect
Hard rule: address spaces must never overlap
VNet to VNet over Azure's backbone
VNet peering
Private IPs, no gateway; non-transitive, so drives hub-and-spoke; billed per GB each direction
To on-prem, encrypted over the internet, fast to stand up
VPN Gateway
IPsec tunnel; ~650 Mbps (VpnGw1) to ~10 Gbps (VpnGw5); ~45-min provision
To on-prem, private, mission-critical, high bandwidth
ExpressRoute
Dedicated circuit, never touches public internet; SLA; up to 100 Gbps; weeks to provision
Peering is non-transitive: spokes reach each other only through the hub, never directly.

Application security groups: label the workload, forget the address

Rules written against IP addresses rot. Autoscaling adds machines, addresses shift, someone forgets to update the NSG, and now a rule is guarding an address nobody uses. An application security group (ASG) fixes that by being a name you stick on a machine, something like asg-app or asg-db. You attach the label to a VM's network interface, then write NSG rules that reference the label in the place an address range would normally sit. The badge, not the room number. When the app tier grows from three machines to thirty, the label travels with every new interface and you change exactly zero rules. Two limits matter, on the exam and in production. Every network interface in one ASG has to live in the same VNet. And if a rule uses ASGs for both source and destination, both groups have to sit in that same VNet as well. ASGs stop at the VNet boundary and do not stretch across a peering.

03-asgs.azcli
# Role labels instead of IP lists
az network asg create -g net-rg -n asg-app -l eastus2 -o none
az network asg create -g net-rg -n asg-db -l eastus2 -o none
# Tag each VM's NIC into its role (repeat as tiers scale — rules never change).
# az vm create names the NIC "<vm>VMNic" and its IP config "ipconfig<vm>".
az network nic ip-config update -g net-rg --nic-name app-vm-1VMNic \
-n ipconfigapp-vm-1 --application-security-groups asg-app -o none
# Data tier: only the app role reaches Postgres — no IPs anywhere in the rule
az network nsg create -g net-rg -n data-nsg -o none
az network nsg rule create -g net-rg --nsg-name data-nsg -n allow-app-to-db \
--priority 100 --direction Inbound --access Allow --protocol Tcp \
--source-asgs asg-app --destination-asgs asg-db \
--destination-port-ranges 5432 -o none

Prove it rather than trust it: Network Watcher

"The NSG looks right" is not evidence. Network Watcher is Azure's built-in network diagnostics service, one per region, switched on for you the moment you create or update a VNet in that region. It answers the two questions that end most connectivity arguments. Would this exact packet get through, yes or no? And which rules is this network interface really enforcing at this moment? The second question matters more than it sounds, because the effective rules are the subnet NSG, the NIC NSG and the built-in defaults merged into one ordered list. The portal shows you one NSG at a time. The interface enforces the union of all of them.

04-verify.azcli
# Would this exact packet get through? (the VM must be running)
az network watcher test-ip-flow -g net-rg --vm web-vm-1 \
--direction Inbound --protocol TCP \
--local 10.20.1.4:443 --remote 203.0.113.50:60000
# {
# "access": "Allow",
# "ruleName": "securityRules/allow-https"
# }
# The merged rule set the NIC enforces: subnet NSG + NIC NSG + defaults
az network nic list-effective-nsg -g net-rg -n web-vm-1VMNic \
--query "value[0].effectiveSecurityRules[].{Rule:name, Access:access, Priority:priority}" \
-o table
# Rule Access Priority
# securityRules/allow-https Allow 100
# securityRules/deny-all-in Deny 4096
# defaultSecurityRules/DenyAllInBound Deny 65500
Never open RDP or SSH to the internet
An NSG rule that opens port 3389 (RDP, Remote Desktop Protocol) or port 22 (SSH, Secure Shell) to Internet or to * puts your admin door on every scanner's map. Give that VM a public IP and the brute-force login attempts start within minutes, not days. Microsoft Defender for Cloud will raise its high-severity recommendation telling you to protect management ports with just-in-time access, and it is right to. Deny management ports from the internet outright, and reach your machines through Azure Bastion or Just-in-Time VM access instead. Remove the standing exposure rather than betting the environment on how strong a password is.

Connecting buildings: peering, VPN Gateway, ExpressRoute

VNet peering is a private corridor built between two buildings. Join two VNets with it and their resources talk over Azure's own backbone using private IP addresses. No gateway to deploy, no trip across the public internet, and inside a single region the latency is the same as if everything sat in one VNet. Peering is non-transitive, and that is the part people trip over. A peers with B, B peers with C, and A still cannot reach C. That one limitation is why the hub-and-spoke layout runs so much production traffic: a central hub VNet holds the shared kit (firewall, DNS, the VPN gateway) and every spoke peers with the hub and nothing else. Peering crosses regions (*global peering*), and it crosses subscriptions and even tenants. The hard stop is overlapping address space, which is why you planned those CIDR ranges carefully back in the first section. Watch the bill, too. Peering carries no hourly charge, but you pay per gigabyte in *each* direction, and global rates run several times the in-region ones. A chatty pair of cross-region services adds up faster than anyone expects.

05-peering.azcli
# Peering is two one-way links — create both, or the state stays "Initiated"
az network vnet peering create -g net-rg -n hub-to-spoke \
--vnet-name hub-vnet --remote-vnet spoke-vnet --allow-vnet-access -o none
az network vnet peering create -g net-rg -n spoke-to-hub \
--vnet-name spoke-vnet --remote-vnet hub-vnet --allow-vnet-access -o none
az network vnet peering show -g net-rg --vnet-name hub-vnet \
-n hub-to-spoke --query peeringState -o tsv
# Connected

Reaching your own datacenter is a different problem with two answers. A VPN Gateway digs an encrypted IPsec (Internet Protocol Security, the standard for encrypting traffic between two networks) tunnel across the public internet. It is cheap to start with and straightforward to configure. The catches: the gateway itself often takes 45 minutes or more to provision, and throughput is capped by the SKU (stock keeping unit, Azure's word for a size and price tier) you choose, roughly 650 Mbps (megabits per second) on VpnGw1 and about 10 Gbps (gigabits per second) aggregate on VpnGw5. Azure is moving those to equivalent zone-redundant AZ SKUs at the same throughput. ExpressRoute is the other answer: a private, dedicated circuit ordered through a connectivity provider that never touches the public internet at all. You get more bandwidth (up to 10 Gbps through a provider, 100 Gbps with ExpressRoute Direct), a formal SLA (service level agreement), and latency you can predict. You also get weeks of provisioning and a serious monthly bill. The shortcut, for the exam and for real decisions: encrypted over the internet and needed this month means VPN Gateway; private, mission-critical and bandwidth-hungry means ExpressRoute. For private access to platform services like Storage or SQL, Private Endpoints pull the service inside your VNet, and that technique gets its own lesson.

Two gaps are still open in this building. Inbound traffic lands on one web VM at a time instead of being spread across the tier, and now that management ports are shut to the internet, your admins have no legitimate way in at all. The next lesson closes both: Load balancing & Bastion.

Two details worth carrying with you. Subnets host Private Endpoints and delegated subnets as well as plain virtual machines. A delegated subnet is one you hand over to a managed service (App Service VNet integration, Azure Database for PostgreSQL and friends), and that service then owns it, so leave a spare range for delegation while the VNet is still mostly empty. Carving one out of a full address space later is miserable work.

The other detail is what happens when spoke-to-spoke traffic finally has to flow. The hub does not forward packets on its own. You place something in it that routes, a network virtual appliance or Azure Firewall, and point user-defined routes at that device so spoke traffic is steered through it and can be inspected. Service endpoints and Private Endpoints are the other way to pull platform services into your address space, and the Private Endpoints lesson goes deeper on both.

Try this

Build a small lab you can throw away afterwards. One VNet with two subnets, an NSG on the app subnet that denies SSH from the internet, and then list the effective rules once the NSG is attached, so you can watch your rule sit above the defaults and see the order Azure will read them in.

terminal
RG=rg-lab-vnet
az group create -n $RG -l eastus
az network vnet create -g $RG -n vnet-lab --address-prefix 10.10.0.0/16 \
--subnet-name snet-app --subnet-prefix 10.10.1.0/24
az network vnet subnet create -g $RG --vnet-name vnet-lab -n snet-data --address-prefix 10.10.2.0/24
az network nsg create -g $RG -n nsg-app
az network nsg rule create -g $RG --nsg-name nsg-app -n DenySSHInternet --priority 100 \
--access Deny --protocol Tcp --direction Inbound --source-address-prefixes Internet \
--destination-port-ranges 22
az network vnet subnet update -g $RG --vnet-name vnet-lab -n snet-app --network-security-group nsg-app
az network nsg rule list -g $RG --nsg-name nsg-app -o table
output
$ az network nsg rule list -g rg-lab-vnet --nsg-name nsg-app -o table
Name Priority Access Direction DestPort
---------------- -------- ------ --------- --------
DenySSHInternet 100 Deny Inbound 22
AllowVnetInBound 65000 Allow Inbound *
AllowAzureLoadB… 65001 Allow Inbound *
DenyAllInBound 65500 Deny Inbound *
# Sample output — lower priority number wins when rules conflict.

Takeaway

A VNet is your private address space. Subnets carve it into floors. NSGs decide who gets through a door, lowest priority number first, first match wins. Peering and gateways connect one building to the next. Plan the address ranges before you build anything, because that is the one decision you cannot quietly fix later.

Next up for practice: peer two VNets, watch spoke-to-spoke traffic fail exactly as the non-transitive rule predicts, then add a hub firewall and routes so the traffic flows and gets inspected on the way.

Quick check
01web-nsg is attached to snet-web with allow-https at priority 100 and an explicit deny-all-in at 4096. Weeks later someone puts the subnet behind an Azure Load Balancer. The health probes start failing immediately and the backend pool drains to zero. What went wrong, and how do you fix it?
Correct — Rules are read from the lowest number upward and the first match wins, so the deny at 4096 fires ahead of the 65001 default and drops probe traffic without logging a word.
Incorrect — Stateful means the opposite: once an inbound flow is allowed, its reply is permitted automatically, so mirror rules are never needed.
Incorrect — Probes come from the AzureLoadBalancer tag (168.63.129.16), which is covered by AllowAzureLoadBalancerInBound at 65001, not by the VirtualNetwork default.
Incorrect — The 65000-and-above defaults can be overridden by a lower-numbered rule, but nothing you do can delete them.
02You create a subnet with a /24 CIDR block, which covers 256 IP addresses. How many of those can you actually hand out to virtual machines?
Incorrect — Azure holds back addresses in every subnet, so the full block is never yours to assign.
Incorrect — That is the on-premises convention. Azure reserves five addresses, not two.
Correct — Azure keeps the network address, the gateway at .1, two for Azure DNS and the broadcast address, which leaves 251 usable in a /24.
Incorrect — Azure reserves five addresses per subnet, not one.
03A bank needs its on-premises datacenter connected to Azure over a private, dedicated link that never crosses the public internet, comes with a formal SLA, and carries very high bandwidth for a mission-critical workload. Waiting several weeks for provisioning is acceptable. Which option is the BEST fit?
Incorrect — A VPN Gateway builds an encrypted tunnel across the public internet with throughput capped by SKU, so it is neither private nor dedicated.
Incorrect — Peering joins two Azure VNets over the backbone. It does not reach an on-premises datacenter at all.
Incorrect — Global peering links Azure VNets in different regions, not an on-premises site.
Correct — ExpressRoute is a private dedicated circuit that never touches the public internet, carries an SLA and up to 100 Gbps, and takes weeks to provision, which matches every requirement in the question.

Related