Network Security Groups
Why this matters
An NSG is how you actually control traffic in Azure — without one, subnets rely only on default rules that allow all intra-VNet traffic and all outbound internet. Nearly every "why can't I reach my VM/app?" problem in Azure comes down to an NSG rule, its priority, or which resource it's attached to. This is the firewall concept from track 1, implemented as an Azure resource with some very specific ordering and default-rule behavior you must know cold.
Concepts
An NSG is Azure's stateful firewall
An NSG is Azure's implementation of the stateful packet-filtering firewall from networking-fundamentals module 05. It holds a prioritized list of allow/deny rules for inbound and outbound traffic, matching on source/destination IP (or tag), port, and protocol. Because it's stateful, you only write a rule for the initiating direction — if you allow inbound TCP 443, the return traffic is automatically permitted; you don't write a matching outbound rule for responses.
Attachment: subnet vs NIC
An NSG can be associated to a subnet (applies to every resource in it) or to a NIC (applies to that one resource), or both. When both apply, inbound traffic is evaluated against the subnet NSG then the NIC NSG (and the reverse order outbound); traffic must pass both. Prefer subnet-level NSGs for broad policy and use NIC-level sparingly — two NSGs on the same path is a classic source of "it's allowed in one place but still blocked" confusion.
Rules, priority, and evaluation order
Each rule has a priority from 100 to 4096 (lower number = evaluated first).
Rules are processed in priority order and the first match wins — once a
packet matches a rule, no lower-priority rule is consulted. This is the detail
that breaks people: a broad Deny at priority 200 will shadow a specific
Allow at priority 300, because 200 is evaluated first. Leave gaps between
priorities (100, 200, 300...) so you can insert rules later.
Default rules and service tags
Every NSG has hidden default rules at priorities 65000–65500 you can't
delete (only override with higher-priority rules): allow all inbound within the
VNet (AllowVnetInBound), allow inbound from Azure Load Balancer, deny all
other inbound (DenyAllInBound); and outbound, allow all VNet + all internet,
deny the rest. Rules reference service tags — symbolic names like
VirtualNetwork, Internet, AzureLoadBalancer, Storage, Sql — instead
of hardcoded IP ranges. Use tags instead of IP literals wherever possible;
Azure keeps the underlying ranges current for you.
Application Security Groups (ASGs)
An ASG lets you group NICs by role (e.g. web-servers, db-servers) and
then write NSG rules whose source/destination is the ASG name instead of an IP
range. Add a new web VM to the web-servers ASG and it inherits the rules — no
NSG edits. ASGs make rules readable and stable as your fleet changes; they're
the Azure answer to "firewall rules that reference roles, not addresses."
Command reference
| Command | What it does | Example |
|---|---|---|
az network nsg create |
Creates an NSG | az network nsg create -g nsg-lab-rg -n web-nsg |
az network nsg rule create |
Adds a rule to an NSG | az network nsg rule create -g nsg-lab-rg --nsg-name web-nsg -n allow-https --priority 300 --access Allow --direction Inbound --protocol Tcp --destination-port-ranges 443 --source-address-prefixes Internet |
az network nsg rule list |
Lists rules (add --include-default) |
az network nsg rule list -g nsg-lab-rg --nsg-name web-nsg --include-default -o table |
az network nsg rule update |
Edits a rule | az network nsg rule update -g nsg-lab-rg --nsg-name web-nsg -n allow-https --priority 250 |
az network vnet subnet update |
Attaches an NSG to a subnet | az network vnet subnet update -g nsg-lab-rg --vnet-name lab-vnet -n web-subnet --network-security-group web-nsg |
az network asg create |
Creates an Application Security Group | az network asg create -g nsg-lab-rg -n web-servers |
az network watcher test-ip-flow |
Simulates whether traffic is allowed/denied | az network watcher test-ip-flow -g nsg-lab-rg --vm myvm --direction Inbound --protocol TCP --local 10.0.1.4:443 --remote 8.8.8.8:12345 |
Flag breakdown — az network nsg rule create ... -n allow-https --priority 300 --access Allow --direction Inbound --protocol Tcp --destination-port-ranges 443 --source-address-prefixes Internet:
-n(--name): rule name.--priority: 100–4096; lower is evaluated first; must be unique per direction in the NSG.--access:AlloworDeny.--direction:InboundorOutbound.--protocol:Tcp,Udp,Icmp, or*(any).--destination-port-ranges: port(s) the traffic is going to (e.g.443,80 443,1000-2000).--source-address-prefixes: source IP CIDR(s) or a service tag likeInternetorVirtualNetwork.
Flag breakdown — az network watcher test-ip-flow -g ... --vm myvm --direction Inbound --protocol TCP --local 10.0.1.4:443 --remote 8.8.8.8:12345:
--vm: the VM whose effective NSG rules are evaluated.--direction: direction of the simulated packet.--local: the VM-sideIP:port.--remote: the other end'sIP:port.- Returns
Allow/Denyand the name of the matching rule — the single best NSG debugging tool.
Hands-on exercises
Set up a group, VNet, and subnet:
az group create --name nsg-lab-rg --location eastus
az network vnet create -g nsg-lab-rg -n lab-vnet \
--address-prefixes 10.0.0.0/16 \
--subnet-name web-subnet --subnet-prefixes 10.0.1.0/24
Create an NSG and attach it to the subnet.
az network nsg create -g nsg-lab-rg -n web-nsg az network vnet subnet update -g nsg-lab-rg --vnet-name lab-vnet \ -n web-subnet --network-security-group web-nsgVerify:
az network vnet subnet show -g nsg-lab-rg --vnet-name lab-vnet -n web-subnetnow shows anetworkSecurityGroupreference.Inspect the default rules.
az network nsg rule list -g nsg-lab-rg --nsg-name web-nsg \ --include-default -o tableVerify: you see
AllowVnetInBound,AllowAzureLoadBalancerInBound,DenyAllInBound(65000–65500) and the outbound equivalents. You have no custom rules yet.Allow HTTPS from the internet.
az network nsg rule create -g nsg-lab-rg --nsg-name web-nsg \ -n allow-https --priority 300 --access Allow --direction Inbound \ --protocol Tcp --destination-port-ranges 443 \ --source-address-prefixes InternetVerify: rule list (without
--include-default) showsallow-https.Allow SSH only from your IP. Replace
<your-ip>with your actual public IP (find it at whatismyip orcurl ifconfig.me):az network nsg rule create -g nsg-lab-rg --nsg-name web-nsg \ -n allow-ssh-mgmt --priority 310 --access Allow --direction Inbound \ --protocol Tcp --destination-port-ranges 22 \ --source-address-prefixes <your-ip>/32This is far safer than opening 22 to
Internet.Use service tags instead of IP ranges. Add an outbound rule allowing only Azure Storage:
az network nsg rule create -g nsg-lab-rg --nsg-name web-nsg \ -n allow-storage-out --priority 300 --access Allow --direction Outbound \ --protocol Tcp --destination-port-ranges 443 \ --destination-address-prefixes StorageNote you referenced the
Storagetag, not any IP range.Create and use an ASG.
az network asg create -g nsg-lab-rg -n web-serversThen rewrite the HTTPS rule to target the ASG as destination instead of the whole subnet:
az network nsg rule update -g nsg-lab-rg --nsg-name web-nsg \ -n allow-https --destination-asgs web-servers(In a real deployment you'd add each web NIC to
web-servers; here you're learning the wiring.)Diagnose and fix: a broad deny shadows a specific allow. Add a deliberately misordered pair of rules:
az network nsg rule create -g nsg-lab-rg --nsg-name web-nsg \ -n deny-all-web --priority 200 --access Deny --direction Inbound \ --protocol Tcp --destination-port-ranges 80 443 \ --source-address-prefixes InternetNow your
allow-httpsat priority 300 never fires, becausedeny-all-webat 200 is evaluated first and matches 443. Diagnose: list rules and note the priority ordering — 200 < 300, first match wins, so 443 is denied. Fix: either raiseallow-httpsabove the deny, or narrow the deny. Give the allow a lower (earlier) priority than the deny:az network nsg rule update -g nsg-lab-rg --nsg-name web-nsg \ -n allow-https --priority 150Now 443 is allowed (150) before the deny (200) is reached. This priority-ordering trap is the most common NSG bug in production.
Verify with the flow simulator (if you have a VM). If you place a VM in the subnet,
az network watcher test-ip-flowtells you the verdict and the matching rule name — the definitive way to confirm exercise 7's fix rather than guessing. Without a VM, reason through the priority list instead.Cleanup. NSGs are free, but delete the group to stay tidy:
az group delete --name nsg-lab-rg --yes --no-wait
Independent challenge
Build a two-tier NSG design combining this module with module 01. In a
fresh resource group, create a VNet with a web-subnet and a db-subnet. Put
an NSG on each. The web NSG should allow HTTPS from the internet and SSH only
from your IP; the db NSG should allow the database port (say TCP 5432) only
from the web subnet's address range and deny it from everywhere else,
including the internet. Prove the ordering is correct by listing the effective
rules and reasoning about which fires first. Clean up the resource group
afterward.
Stuck? One hint
For the db rule, set --source-address-prefixes to the web subnet's CIDR (or
better, use the VirtualNetwork service tag combined with a higher-priority
deny for Internet). Remember NSGs are stateful — you do not need a
corresponding outbound rule for the database's replies. Confirm the ordering
by listing rules with --include-default and checking that no broad deny sits
at a lower priority number than your allows.
Common mistakes & troubleshooting
- Priority ordering. Lowest number wins and first match stops evaluation. A broad deny at a low priority silently shadows specific allows above it in number. This is the NSG bug.
- Two NSGs on the same path. Traffic must pass both the subnet NSG and the NIC NSG. If one allows and the other denies, it's denied — check both.
- Opening management ports to
Internet. Never open 22/3389 toInternet; scope to your IP or use a bastion. - Forgetting statefulness. You do not write return-path rules. Adding redundant outbound "response" rules just adds confusion.
- Editing default rules. You can't delete them; you override by adding a higher-priority (lower-number) custom rule.
- Cost note: NSGs themselves are free, so leaving one attached costs nothing — but a misconfigured NSG that blocks a health probe can make a billable Load Balancer or App Gateway (later modules) look broken while still charging you. Debug the NSG first.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- An NSG has an
Allowrule for TCP 443 at priority 300 and aDenyrule for TCP 443 at priority 200. Is 443 allowed or denied? Why? - Why don't you need to write an outbound rule to permit the responses to an allowed inbound connection?
- What's the difference between attaching an NSG to a subnet vs. a NIC, and what happens when both are present?
- What is a service tag, and give two examples. Why prefer them over IP literals?
- Which default rule blocks inbound internet traffic, and at what priority range? Can you delete it?
- What problem do Application Security Groups solve?
- What single
azcommand tells you both whether traffic is allowed and which rule decided it?
Show answers
- Denied. Priority 200 is evaluated before 300, and the first matching rule wins, so the deny fires and the allow is never reached.
- NSGs are stateful; the return traffic of an allowed flow is automatically permitted.
- Subnet NSG applies to all resources in the subnet; NIC NSG applies to one resource. When both exist, traffic must pass both (subnet then NIC inbound; reverse outbound). If either denies, it's denied.
- A symbolic name for an Azure-maintained set of IP ranges (e.g.
Internet,VirtualNetwork,Storage,AzureLoadBalancer). They stay current automatically and are more readable than hardcoded CIDRs. DenyAllInBound, in the 65000–65500 default range. You can't delete it; you override it with a higher-priority custom allow.- They let rules reference role-based groups of NICs instead of IP ranges, so rules stay stable as machines are added/removed.
az network watcher test-ip-flow.
Next
03 — Azure DNS & Private DNS: host public and private DNS zones and see why a private zone silently fails to resolve until it's linked to your VNet.