Azure Firewall & Hub-Spoke
Why this matters
This module ties everything together into the topology real Azure networks actually use: hub-and-spoke, with a central Azure Firewall controlling egress and inter-spoke traffic. It's where peering (module 05), route tables, and firewall policy combine — and where you finally make spoke-to-spoke traffic work despite peering's non-transitivity. Azure Firewall is the second resource in this track that bills hourly even when idle, and it's not cheap, so cost discipline is at its peak here.
Concepts
Azure Firewall
Azure Firewall is a managed, stateful, cloud-scale network firewall — the
"centralized egress firewall / perimeter" concept from track 1, delivered as a
service. Unlike an NSG (which is a distributed L3/L4 filter attached to subnets/
NICs), Azure Firewall is a centralized appliance with a private IP in the
hub that traffic is routed through. It supports network rules (L3/L4 by
IP/port/protocol), application rules (L7 by FQDN — e.g. allow egress only
to *.ubuntu.com), and NAT rules (DNAT inbound). It provides FQDN-based
egress filtering that NSGs can't, plus centralized logging. It bills a
significant fixed hourly rate plus per-GB processed.
NSG vs. Azure Firewall — when to use which
They're complementary, not competing. NSGs are free, distributed, and good for subnet/NIC-level allow/deny on IP and port — your first line of segmentation. Azure Firewall is centralized, billed, and adds FQDN filtering, application rules, threat intelligence, and unified logging across the whole network. A typical design uses both: NSGs on every subnet for micro-segmentation, and a single Firewall in the hub for controlled, logged egress and inter-spoke traffic. Don't reach for Firewall where an NSG suffices — it's expensive.
Route tables (UDRs) force traffic through the firewall
By default, Azure's system routes send subnet traffic straight out to the
internet or directly across peerings — bypassing the firewall. To force
traffic through the hub firewall you attach a route table (UDR) to each
spoke subnet with a route like 0.0.0.0/0 → next hop = the firewall's private IP (next hop type VirtualAppliance). This is the piece people forget: you
can deploy a perfect firewall and see zero traffic hit it because no route
sends traffic its way. Routing, not the firewall itself, is what puts the
firewall in the path.
Hub-and-spoke topology
The hub-and-spoke pattern: a central hub VNet holds shared services (the firewall, and often a VPN/ExpressRoute gateway and DNS), and each workload lives in its own spoke VNet peered to the hub. Spokes don't peer to each other; instead, spoke-to-spoke traffic is routed through the hub firewall (recall from module 05 that peering isn't transitive). This centralizes egress control, logging, and shared services while keeping workloads isolated in their own VNets. It's the standard enterprise Azure landing-zone shape.
Making spoke-to-spoke work
To let spoke-A reach spoke-B through the hub firewall you need three things
together: (1) both spokes peered to the hub with --allow-forwarded-traffic
enabled so the hub can forward their traffic, (2) a UDR on each spoke subnet
routing the other spoke's range (or 0.0.0.0/0) to the firewall's private IP,
and (3) a firewall network rule permitting spoke-A ↔ spoke-B. Miss any one and
the traffic silently drops or takes the wrong path.
Command reference
| Command | What it does | Example |
|---|---|---|
az network firewall create |
Creates an Azure Firewall | az network firewall create -g hub-lab-rg -n hub-fw --vnet-name hub-vnet |
az network firewall ip-config create |
Assigns the firewall a public IP + subnet | az network firewall ip-config create -g hub-lab-rg -f hub-fw -n fw-ipconfig --public-ip-address fw-pip --vnet-name hub-vnet |
az network firewall network-rule create |
Adds an L3/L4 network rule | az network firewall network-rule create -g hub-lab-rg -f hub-fw --collection-name net-coll --name allow-spoke --protocols TCP --source-addresses 10.1.0.0/16 --destination-addresses 10.2.0.0/16 --destination-ports 443 --action Allow --priority 100 |
az network firewall application-rule create |
Adds an L7 FQDN rule | az network firewall application-rule create -g hub-lab-rg -f hub-fw --collection-name app-coll --name allow-ubuntu --protocols Http=80 Https=443 --source-addresses 10.1.0.0/16 --target-fqdns *.ubuntu.com --action Allow --priority 200 |
az network route-table create |
Creates a route table (UDR) | az network route-table create -g hub-lab-rg -n spokeA-rt |
az network route-table route create |
Adds a route | az network route-table route create -g hub-lab-rg --route-table-name spokeA-rt -n to-fw --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4 |
az network vnet subnet update |
Attaches a route table to a subnet | az network vnet subnet update -g hub-lab-rg --vnet-name spokeA-vnet -n app-subnet --route-table spokeA-rt |
Flag breakdown — az network firewall network-rule create ... --collection-name net-coll --name allow-spoke --protocols TCP --source-addresses 10.1.0.0/16 --destination-addresses 10.2.0.0/16 --destination-ports 443 --action Allow --priority 100:
--collection-name: rule collection to add to (created on first rule);--priorityand--actionapply at the collection level.--protocols:TCP/UDP/ICMP/Any.--source-addresses/--destination-addresses: CIDR(s) for each end.--destination-ports: allowed destination port(s).--action:AlloworDenyfor the collection.--priority: collection priority (100–65000; lower first).
Flag breakdown — az network route-table route create ... -n to-fw --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4:
--address-prefix: which destination range this route matches (0.0.0.0/0= all traffic / default route).--next-hop-type:VirtualAppliance(send to an IP like a firewall),Internet,VnetLocal,VirtualNetworkGateway, orNone.--next-hop-ip-address: the firewall's private IP (required when next hop isVirtualAppliance).
Hands-on exercises
Cost warning: Azure Firewall bills a substantial hourly rate plus per-GB the entire time it exists — this is the most expensive resource in the track. Do this module in one sitting and delete the group immediately after.
Build the hub and two spokes (non-overlapping ranges), with the firewall's mandatory subnet:
az group create --name hub-lab-rg --location eastus
az network vnet create -g hub-lab-rg -n hub-vnet --address-prefixes 10.0.0.0/16 \
--subnet-name AzureFirewallSubnet --subnet-prefixes 10.0.1.0/26
az network vnet create -g hub-lab-rg -n spokeA-vnet --address-prefixes 10.1.0.0/16 \
--subnet-name app-subnet --subnet-prefixes 10.1.1.0/24
az network vnet create -g hub-lab-rg -n spokeB-vnet --address-prefixes 10.2.0.0/16 \
--subnet-name app-subnet --subnet-prefixes 10.2.1.0/24
Peer both spokes to the hub with forwarding allowed.
az network vnet peering create -g hub-lab-rg -n hub-to-A \ --vnet-name hub-vnet --remote-vnet spokeA-vnet \ --allow-vnet-access --allow-forwarded-traffic az network vnet peering create -g hub-lab-rg -n A-to-hub \ --vnet-name spokeA-vnet --remote-vnet hub-vnet \ --allow-vnet-access --allow-forwarded-traffic az network vnet peering create -g hub-lab-rg -n hub-to-B \ --vnet-name hub-vnet --remote-vnet spokeB-vnet \ --allow-vnet-access --allow-forwarded-traffic az network vnet peering create -g hub-lab-rg -n B-to-hub \ --vnet-name spokeB-vnet --remote-vnet hub-vnet \ --allow-vnet-access --allow-forwarded-trafficVerify: all four peerings show
Connected.Create the Azure Firewall. (Takes several minutes.)
az network public-ip create -g hub-lab-rg -n fw-pip \ --sku Standard --allocation-method Static az network firewall create -g hub-lab-rg -n hub-fw --vnet-name hub-vnet az network firewall ip-config create -g hub-lab-rg -f hub-fw \ -n fw-ipconfig --public-ip-address fw-pip --vnet-name hub-vnetVerify: capture the firewall's private IP —
az network firewall show -g hub-lab-rg -n hub-fw \ --query "ipConfigurations[0].privateIPAddress" -o tsv(likely
10.0.1.4). You'll route to this IP.Create route tables sending each spoke's traffic to the firewall. Assume the firewall private IP is
10.0.1.4:az network route-table create -g hub-lab-rg -n spokeA-rt az network route-table route create -g hub-lab-rg \ --route-table-name spokeA-rt -n to-fw --address-prefix 0.0.0.0/0 \ --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4 az network vnet subnet update -g hub-lab-rg --vnet-name spokeA-vnet \ -n app-subnet --route-table spokeA-rtRepeat for spokeB (
spokeB-rt, attached to spokeB'sapp-subnet).Verify:
az network vnet subnet show ... -n app-subnet --query routeTablereferences the route table.Add a firewall network rule allowing spokeA ↔ spokeB.
az network firewall network-rule create -g hub-lab-rg -f hub-fw \ --collection-name spoke-coll --name allow-A-to-B --protocols TCP \ --source-addresses 10.1.0.0/16 --destination-addresses 10.2.0.0/16 \ --destination-ports 443 --action Allow --priority 100Now spokeA→spokeB on 443 is routed to the firewall (via the UDR) and permitted by this rule — spoke-to-spoke works despite non-transitive peering.
Add an application rule for controlled egress. Allow spokes to reach only Ubuntu package mirrors over HTTP/HTTPS, nothing else:
az network firewall application-rule create -g hub-lab-rg -f hub-fw \ --collection-name egress-coll --name allow-ubuntu \ --protocols Http=80 Https=443 --source-addresses 10.1.0.0/16 10.2.0.0/16 \ --target-fqdns "*.ubuntu.com" --action Allow --priority 200This is FQDN-based egress filtering — something an NSG cannot do.
Diagnose and fix: firewall deployed but no traffic hits it. Remove the route table from spokeA to simulate the classic mistake:
az network vnet subnet update -g hub-lab-rg --vnet-name spokeA-vnet \ -n app-subnet --remove routeTableNow spokeA's default route is the system route (straight to internet / direct peering), so traffic never reaches the firewall — the firewall rules appear to do nothing and spoke-to-spoke stops flowing through it. Diagnose: the firewall config is correct but the subnet has no UDR pointing at it — routing, not the rules, is the problem. Fix: re-attach the route table:
az network vnet subnet update -g hub-lab-rg --vnet-name spokeA-vnet \ -n app-subnet --route-table spokeA-rtVerify: subnet again references
spokeA-rt. Lesson: a firewall only filters traffic that's routed to it. Without a UDR (0.0.0.0/0 → firewall private IP) on the subnet, the firewall is bypassed entirely.Diagnose and fix: spoke-to-spoke blocked despite the route. Suppose the UDR is in place but spokeA still can't reach spokeB. Two remaining culprits: the peering isn't allowing forwarded traffic, or the firewall has no rule permitting the flow. Check the peering has
--allow-forwarded-traffic(exercise 1) and that a network rule allows the source/destination/port (exercise 4). Reason through which layer is dropping the packet: routing gets it to the firewall; the firewall rule decides allow/deny; the peering's forwarded-traffic flag decides whether the hub may relay it. All three must line up.Cleanup — do this now. Azure Firewall is the priciest thing in the track. Delete the group immediately:
az group delete --name hub-lab-rg --yes --no-waitVerify:
az group show -n hub-lab-rgeventually returns not-found, and check Cost Management to confirm no Firewall or public IP lingers.
Independent challenge
Combine this module with module 04 (NAT Gateway / outbound) and module 03
(private DNS) conceptually. Build a hub with an Azure Firewall and one spoke,
route the spoke's egress through the firewall, and configure an application
rule that allows the spoke to reach exactly one FQDN (say *.microsoft.com)
and denies all other web egress. Then reason about how this centralized,
FQDN-based egress control differs from a NAT Gateway's role (module 04): one
controls what you can reach by name, the other controls which IP your
outbound traffic appears from. Confirm you can explain when you'd use each.
Tear the resource group down the instant you're done — the firewall bills every
hour it exists.
Stuck? One hint
The firewall only sees traffic a UDR sends it (0.0.0.0/0 → firewall private IP on the spoke subnet). Once traffic arrives, an application rule filters
by destination FQDN, while a network rule filters by IP/port — for
"only *.microsoft.com" you want an application rule with --target-fqdns. A NAT
Gateway, by contrast, doesn't filter destinations at all; it just SNATs your
outbound to a stable public IP. Different jobs: FQDN allow-listing vs. source
NAT.
Common mistakes & troubleshooting
- Firewall deployed but bypassed. No UDR means traffic never routes to the
firewall — the rules look inert. Attach a route table with
0.0.0.0/0 → fw private IPto each subnet whose traffic must be inspected. - Spoke-to-spoke silently fails. Needs all three: peering with forwarded traffic, a UDR to the firewall, and a firewall network rule allowing it.
- Wrong firewall subnet name. Must be exactly
AzureFirewallSubnet, recommended/26. - Using Firewall where an NSG suffices. NSGs are free and handle basic subnet allow/deny; the Firewall is for FQDN egress, centralized logging, and inter-spoke control. Don't pay for what an NSG does.
- Cost pitfall (the biggest in the track): Azure Firewall bills a large fixed hourly rate plus per-GB regardless of traffic, and it's easy to forget because it has no obvious "on" indicator. An idle Firewall left over a weekend can be the most expensive mistake in this curriculum. Delete the group the moment you finish, and confirm in Cost Management.
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.
- You deployed an Azure Firewall with correct rules, but your spoke traffic goes straight to the internet and never hits the firewall. What's missing?
- What are the three rule types Azure Firewall supports, and which one filters by destination FQDN?
- Name the three things that must all be in place for spoke-A to reach spoke-B through the hub firewall.
- What must the firewall's subnet be named, and what's the recommended size?
- Give two ways Azure Firewall differs from an NSG.
- In a route table, what
--next-hop-typeand--next-hop-ip-addressdo you use to send0.0.0.0/0through the firewall? - Why is "use an NSG instead of the Firewall where possible" good advice?
Show answers
- A route table (UDR) on the spoke subnet with
0.0.0.0/0 → firewall private IP(next hopVirtualAppliance). The firewall only filters traffic that's routed to it. - Network rules (L3/L4 by IP/port/protocol), application rules (L7 by FQDN), and NAT rules (DNAT). Application rules filter by FQDN.
- (1) Both spoke↔hub peerings with
--allow-forwarded-traffic, (2) a UDR on each spoke routing the other spoke (or0.0.0.0/0) to the firewall IP, and (3) a firewall network rule allowing the flow. AzureFirewallSubnet(exact, case-sensitive), recommended/26or larger.- Any two: NSG is distributed/free/L3-L4 on IP-port with no FQDN filtering; Firewall is a centralized appliance, billed hourly + per-GB, adds L7 FQDN application rules, threat intel, and centralized logging.
--next-hop-type VirtualApplianceand--next-hop-ip-address <firewall private IP>.- NSGs are free and handle basic subnet/NIC allow-deny; the Firewall bills a large hourly rate, so reserving it for what only it can do (FQDN egress, centralized logging, inter-spoke control) avoids unnecessary cost.
Cumulative review
Closed-book. Answer all of these in writing before opening the solutions — they mix modules 04 through 07 on purpose.
- A public-facing web app needs URL path-based routing, TLS termination, and a WAF; a separate internal service just needs raw TCP flow distribution across three backends. Which Azure resource for each, and why?
- Your Standard Load Balancer's backends all show unhealthy and your App Gateway returns 502. Name the single root-cause category both failures most commonly share, and the diagnostic command for each.
- You built a hub-spoke with a firewall, correct peerings (forwarded traffic on), and correct firewall rules, but spoke traffic still goes straight to the internet, ignoring the firewall. What's missing and how do you fix it?
- Explain why spoke-A can't reach spoke-B "through the hub" just because both are peered to the hub, and list the three things needed to make it work.
- A VM in a spoke has no outbound internet at all (not even to allowed FQDNs). Give two independent possible causes — one from module 04, one from module 07 — and how you'd distinguish them.
- Contrast an NSG and Azure Firewall on: layer/granularity, centralized vs. distributed, FQDN filtering, and cost.
- You need outbound traffic from a subnet to always appear from one stable public IP, and separately you need to allow-list which FQDNs that subnet can reach. Which resource does each job?
- Which two resources in modules 04–07 bill hourly even when completely idle, and what's your standing habit to avoid surprise charges?
Show answers
- Public web app → Application Gateway (WAF_v2) — L7, so it can do path routing, TLS termination, and WAF. Internal TCP service → Azure Load Balancer (L4, internal/ILB) — cheaper, sufficient for raw flow distribution.
- Category: health-probe / backend-health misconfiguration (probe port/
path/host doesn't match what the backend serves, or an NSG blocks the
probe). LB:
az network watcher test-ip-flowand probe/port check; App Gateway:az network application-gateway show-backend-health. - A route table (UDR) on the spoke subnet with
0.0.0.0/0 → firewall private IP(next hopVirtualAppliance). Without it, system routes bypass the firewall. Attach the UDR withaz network vnet subnet update ... --route-table. - Peering isn't transitive — it only connects directly-peered VNets. Needed:
(1) both spoke↔hub peerings with
--allow-forwarded-traffic, (2) a UDR on each spoke routing the other spoke (or0.0.0.0/0) to the firewall IP, and (3) a firewall network rule allowing spoke-A ↔ spoke-B. - (a) Module 04: no NAT Gateway / outbound path on a Standard-LB subnet, so no SNAT for egress. (b) Module 07: egress is routed to the firewall but no application/network rule allows it (default deny). Distinguish by checking whether a UDR points to the firewall (if yes, it's a firewall rule problem; if no NAT/route at all, it's outbound-path/SNAT).
- NSG: L3/L4 on IP/port, distributed on subnets/NICs, no FQDN filtering, free. Azure Firewall: L3/L4 + L7 FQDN application rules, centralized appliance in the hub, FQDN filtering yes, bills hourly + per-GB.
- Stable outbound source IP → NAT Gateway (module 04). FQDN allow-listing → Azure Firewall application rules (module 07).
- Application Gateway (v2) and Azure Firewall. Standing habit: delete the
resource group with
az group deletethe moment the exercise ends, and verify in Cost Management that nothing lingers.
Next
08 — Capstone project: assemble a full hub-and-spoke design end to end, verify traffic flows and is blocked where it should be, then tear it all down.