Virtual networks & NSGs
Subnets, NSGs/ASGs, peering, gateways.
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.
# Resource group + VNet with three tiered subnetsaz group create -n net-rg -l eastus2 -o noneaz 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 noneaz network vnet subnet create -g net-rg --vnet-name prod-vnet \-n snet-app --address-prefixes 10.20.2.0/24 -o noneaz network vnet subnet create -g net-rg --vnet-name prod-vnet \-n snet-data --address-prefixes 10.20.3.0/24 -o noneaz 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.
# Web tier NSG: HTTPS in from the internet, explicit deny for everything elseaz network nsg create -g net-rg -n web-nsg -o noneaz 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 noneaz 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 rulesaz network vnet subnet update -g net-rg --vnet-name prod-vnet \-n snet-web --network-security-group web-nsg -o noneaz 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.
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.
# Role labels instead of IP listsaz network asg create -g net-rg -n asg-app -l eastus2 -o noneaz 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 ruleaz network nsg create -g net-rg -n data-nsg -o noneaz 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.
# 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 + defaultsaz 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
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.
# 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 noneaz network vnet peering create -g net-rg -n spoke-to-hub \--vnet-name spoke-vnet --remote-vnet hub-vnet --allow-vnet-access -o noneaz 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.
RG=rg-lab-vnetaz group create -n $RG -l eastusaz 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/24az network vnet subnet create -g $RG --vnet-name vnet-lab -n snet-data --address-prefix 10.10.2.0/24az network nsg create -g $RG -n nsg-appaz 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 22az network vnet subnet update -g $RG --vnet-name vnet-lab -n snet-app --network-security-group nsg-appaz network nsg rule list -g $RG --nsg-name nsg-app -o table
$ az network nsg rule list -g rg-lab-vnet --nsg-name nsg-app -o tableName Priority Access Direction DestPort---------------- -------- ------ --------- --------DenySSHInternet 100 Deny Inbound 22AllowVnetInBound 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.