Build a Hub-Spoke Network in Azure

Design a 3-VNet hub-spoke topology with peering, NSGs, and custom routes.

Introduction

30 Second Summary

Large organizations route traffic between teams through a central point, like an airport hub connecting flights between cities that have no direct route. In cloud networking, this same pattern keeps workloads isolated while still letting them communicate through a controlled center.

In this project, you will build a hub-spoke network in Azure with three virtual networks, configure peering and security rules, and verify everything using diagnostic tools.

What You'll Build

A production-style hub-spoke network architecture with three virtual networks connected via VNet peering, secured with Network Security Groups, and verified with Network Watcher diagnostics.

By the end of this project, you'll have:

  • A 3-VNet hub-spoke topology with planned IP addressing and bidirectional peering verified as Connected.
  • Custom NSG rules controlling traffic flow, proven working with IP flow verify.
  • A User Defined Route table forcing spoke-to-spoke traffic through the hub, confirmed with Network Watcher Next Hop.
  • Secret Mission: Add an Azure Private DNS zone for cross-spoke name resolution.

Are there any prerequisites?

You need an Azure free tier account with an active subscription (includes $200 credit for 30 days). You should also have networking fundamentals covered, such as through Cisco Networking Academy or equivalent experience with IP addressing and subnets.

Set Up Your Azure Environment

Before you can build a network, you need a workspace. The Azure Portal is your web-based control panel for creating and managing all Azure resources.

In this step, you will create a resource group to organize everything, then open Azure Cloud Shell so you can run commands directly from your browser.

In this step, get ready to:
  • Sign in to the Azure Portal and create a resource group.
  • Open and configure Azure Cloud Shell.
  • Verify your subscription and set CLI defaults.
Sign in to the Azure Portal and orient yourself

A resource group is a container that holds related Azure resources together. You will place every resource in this project inside one group so you can manage and delete them as a unit.

  • Open your browser and navigate to portal.azure.com.
  • Sign in with your Azure free tier account credentials.
  • Locate the search bar at the top of the page. You will use this to find every Azure service throughout this project.
  • Type Resource groups in the search bar and select Resource groups from the results.
  • Click Create.
  • For Subscription, select your Azure free tier subscription.
  • For Resource group, enter rg-hub-spoke.
  • For Region, select (US) East US.
  • Click Review + create.
  • Click Create.

What is a resource group?

A resource group is a logical container in Azure. It groups related resources (VNets, VMs, NSGs) so you can deploy, monitor, and delete them together.

The region you pick for the resource group determines where its metadata is stored. All resources inside it can still be in different regions, but keeping them in the same region simplifies management.

After a few seconds, the resource group is created. You can confirm it by clicking Refresh on the Resource groups page and seeing rg-hub-spoke in the list.

Open Azure Cloud Shell

Cloud Shell is a browser-based terminal built into the Azure Portal. It comes with the Azure CLI pre-installed, so you can run commands without installing anything locally.

  • Click the Cloud Shell icon (the terminal icon that looks like >_) in the top menu bar of the Azure Portal.
  • When prompted to choose a shell, select Bash.
  • In the Getting started pane, select Mount storage account.
  • Select your subscription from the dropdown and click Apply.
  • In the Mount storage account pane, select We will create a storage account for you and click Next.

Why does Cloud Shell need a storage account?

Cloud Shell creates a small storage account so your files persist between sessions. This uses minimal free tier storage and costs only pennies per month.

After a moment, a Bash terminal appears at the bottom of the portal. Confirm it is working by running this command:

  • Verify Cloud Shell is connected to your account by running:
az account show

You should see JSON output showing your subscription name and ID. This confirms Cloud Shell is authenticated and ready.

Cloud Shell not loading?

If Cloud Shell shows an error about storage creation, make sure you selected the correct subscription (your free tier one). Try closing Cloud Shell and reopening it from the top menu bar.

If you see a blank terminal or a spinning indicator for more than 30 seconds, refresh the browser page and click the Cloud Shell icon again.

help me troubleshoot Cloud Shell not loading in the Azure Portal

Verify your subscription and set defaults

Setting CLI defaults saves you from typing the resource group name and region on every command for the rest of this project.

  • List your subscriptions to confirm your free tier account is active by running:
az account list --output table

You should see a table with at least one row. The State column should show Enabled for your free tier subscription.

  • Set your default location and resource group by running:
az configure --defaults location=eastus group=rg-hub-spoke

What does this command do?

This tells the Azure CLI to automatically use eastus as the region and rg-hub-spoke as the resource group for any command that needs them. You will not have to pass --location or --resource-group flags in future commands.

Subscription not showing as Enabled?

If your subscription state shows Disabled or Warned, your free credits may have expired. Check the Cost Management + Billing section in the portal to review your account status.

help me fix my Azure subscription showing as disabled or warned

Your environment is ready. Next up, you will design the IP addressing scheme and create three virtual networks for the hub-spoke topology.

Design and Create the Hub-Spoke VNets

Your Azure environment is ready with a resource group and Cloud Shell configured. Now it's time to build the foundation of your network: three Virtual Networks (VNets) arranged in a hub-spoke topology.

A hub-spoke design places one central VNet (the hub) at the middle, with other VNets (spokes) connecting to it. Before creating anything, you need to plan the IP addressing carefully so that no two VNets share overlapping address ranges.

In this step, get ready to:
  • Plan a non-overlapping IP addressing scheme for the hub and two spokes.
  • Create all three VNets with subnets using Azure CLI commands.
  • Verify the VNets exist with the correct address spaces.
Plan the IP addressing scheme

Every VNet in Azure needs its own address space defined using CIDR notation. For a hub-spoke topology, the address spaces must not overlap, or peering will fail.

Here is the addressing scheme you will use for this project:

  • Hub VNet: 10.0.0.0/16 with a workload subnet 10.0.1.0/24.
  • Spoke 1 VNet: 10.1.0.0/16 with a workload subnet 10.1.1.0/24.
  • Spoke 2 VNet: 10.2.0.0/16 with a workload subnet 10.2.1.0/24.

Why /16 address spaces with /24 subnets?

Each /16 block gives you over 65,000 addresses per VNet. This mirrors real enterprise design where teams need room for future growth (more subnets for gateways, firewalls, app tiers).

The /24 workload subnets give 251 usable host addresses each. Azure reserves 5 addresses per subnet for internal use.

Cisco parallel: How does this map to what I already know?

If you have a Cisco background, think of each VNet as a separate VLAN with its own Layer 3 interface. VNet peering (coming in the next step) maps to inter-VLAN routing on a Layer 3 switch.

Create all three VNets using Cloud Shell

You will create each VNet with a single CLI command that defines both the address space and its workload subnet. Since you set defaults for location and resource group in the previous step, those flags are not needed here.

  • Create the hub VNet by running this command in Cloud Shell:
az network vnet create --name vnet-hub --address-prefixes 10.0.0.0/16 --subnet-name snet-hub-workload --subnet-prefixes 10.0.1.0/24
  • Create the first spoke VNet by running this command:
az network vnet create --name vnet-spoke1 --address-prefixes 10.1.0.0/16 --subnet-name snet-spoke1-workload --subnet-prefixes 10.1.1.0/24
  • Create the second spoke VNet by running this command:
az network vnet create --name vnet-spoke2 --address-prefixes 10.2.0.0/16 --subnet-name snet-spoke2-workload --subnet-prefixes 10.2.1.0/24

What do these flags do?

  • --name sets the VNet's name (you will reference this name in peering commands later).
  • --address-prefixes defines the VNet's overall address space in CIDR notation.
  • --subnet-name and --subnet-prefixes create a subnet inside the VNet in the same command.

Each command outputs a large JSON block when it completes successfully. You should see the VNet's properties including its address space and provisioning state.

Seeing an error after running a command?

  • If you see a message about the resource group not being found, confirm your defaults are set by running az configure --list-defaults --output table. The group should show rg-hub-spoke.
  • If the command hangs or times out, check your internet connection and try running the command again.
  • If you see a naming conflict error, you may have already created the VNet. Continue to the verification step below to check.

Help me troubleshoot my VNet creation error.

Verify VNet creation
  • Confirm all three VNets were created by running this command:
az network vnet list --output table

You should see a table listing three VNets: vnet-hub, vnet-spoke1, and vnet-spoke2. Each row shows the VNet's name, resource group, location, and address prefixes.

What should I see?

The table output shows columns for Name, ResourceGroup, Location, and AddressPrefixes. Confirm each VNet shows its correct address space: 10.0.0.0/16 for the hub, 10.1.0.0/16 for spoke1, and 10.2.0.0/16 for spoke2.

Don't see all three VNets?

  • If only one or two appear, go back and run the missing az network vnet create command again.
  • If the list is empty, your resource group default may not be set. Run az network vnet list --resource-group rg-hub-spoke --output table to explicitly specify the group.

Help me figure out why my VNets aren't showing up.

Your three VNets are in place with clean, non-overlapping address spaces. Next up, you will connect them together using VNet peering so the hub can communicate with both spokes.

Configure VNet Peering

You have three isolated virtual networks sitting in the same region. Right now, resources in one VNet have no way to reach resources in another.

To connect them, you will create VNet peering links between the hub and each spoke. Peering keeps traffic on Microsoft's private backbone instead of routing through the public internet. Each peering must be created in both directions (hub-to-spoke AND spoke-to-hub) before traffic can flow.

In this step, get ready to:
  • Create bidirectional peering between the hub and spoke 1.
  • Create bidirectional peering between the hub and spoke 2.
  • Verify all peering links show Connected status.
Create hub-to-spoke1 peering

VNet peering requires a link from each side. The first link you create shows a state of Initiated until the reverse link is created. Once both exist, Azure transitions both to Connected.

  • Create the peering link from the hub to spoke1 by running this command in Cloud Shell:
az network vnet peering create --name peer-hub-to-spoke1 --vnet-name vnet-hub --remote-vnet vnet-spoke1 --allow-forwarded-traffic true --allow-vnet-access true

What do these flags do?

  • --allow-vnet-access true permits VMs in the peered VNet to reach resources in the local VNet.
  • --allow-forwarded-traffic true lets traffic that did not originate in the peered VNet pass through. This is essential for the hub-spoke pattern where the hub forwards traffic between spokes.
  • Create the reverse link from spoke1 back to the hub by running:
az network vnet peering create --name peer-spoke1-to-hub --vnet-name vnet-spoke1 --remote-vnet vnet-hub --allow-forwarded-traffic true --allow-vnet-access true

The output of this second command should show peeringState as Connected. This confirms both sides are linked.

Seeing a permissions error?

Make sure your free tier subscription is active and that you set the resource group default earlier with az configure --defaults group=rg-hub-spoke. If the command cannot find your VNet, confirm the VNet names match exactly (vnet-hub, vnet-spoke1).

Help me fix a permissions error when creating VNet peering in Azure Cloud Shell.

Create hub-to-spoke2 peering
  • Create the peering link from the hub to spoke2 by running:
az network vnet peering create --name peer-hub-to-spoke2 --vnet-name vnet-hub --remote-vnet vnet-spoke2 --allow-forwarded-traffic true --allow-vnet-access true
  • Create the reverse link from spoke2 back to the hub by running:
az network vnet peering create --name peer-spoke2-to-hub --vnet-name vnet-spoke2 --remote-vnet vnet-hub --allow-forwarded-traffic true --allow-vnet-access true

Confirm the output shows peeringState as Connected for this second pair.

Peering state stuck on Initiated?

If a peering shows Initiated instead of Connected, the reverse link is missing or failed. Re-run the reverse command (the one targeting --vnet-name vnet-spoke2 --remote-vnet vnet-hub). Both directions must exist for the state to transition.

Help me fix VNet peering stuck in Initiated state.

Verify peering status
  • List all peering connections on the hub VNet by running:
az network vnet peering list --vnet-name vnet-hub --output table

You should see two rows in the table output. Both peer-hub-to-spoke1 and peer-hub-to-spoke2 should display Connected in the PeeringState column.

Why is peering nontransitive?

Even though both spokes are peered to the hub, spoke1 cannot reach spoke2 through the hub automatically. VNet peering is nontransitive by design. This provides workload isolation between spokes.

To enable spoke-to-spoke communication, you need a route table (UDR) that explicitly directs traffic through a next-hop device in the hub. You will build this in a later step.

Cisco networking parallel

If you have a Cisco background, think of VNet peering as L3 inter-VLAN routing between switch virtual interfaces. The nontransitive rule means there is no implicit route redistribution between peers. You must add static routes (UDRs) to get traffic flowing between non-directly-peered networks.

Your hub-spoke network is now wired up. Next, you will deploy test VMs into each spoke and apply NSG rules to control which traffic is actually allowed to flow across these peering connections.

Deploy Test VMs and Apply NSG Rules

Your hub-spoke network is peered and ready for traffic. But peering alone does not prove anything is actually flowing between VNets.

In this step, you will deploy lightweight Linux VMs into each spoke subnet, create a Network Security Group to control traffic, and verify the rules using Network Watcher diagnostics. This gives you concrete proof that your security rules are working as designed.

In this step, get ready to:
  • Deploy test VMs in each spoke with private IPs only.
  • Create and associate an NSG with custom inbound rules.
  • Verify NSG rules using Network Watcher diagnostics.
Deploy lightweight Linux VMs in each spoke

To test connectivity between your VNets, you need actual resources sending traffic. You will deploy a small Ubuntu VM in each spoke subnet with no public IP and no auto-created NSG. This mirrors production environments where workloads sit on private subnets only.

  • Deploy the first VM in spoke1 by running this command in Cloud Shell:
az vm create --name vm-spoke1 --vnet-name vnet-spoke1 --subnet snet-spoke1-workload --image Ubuntu2204 --size Standard_B1s --admin-username azureuser --generate-ssh-keys --public-ip-address "" --nsg ""

What does this command do?

  • Creates a VM named vm-spoke1 inside the snet-spoke1-workload subnet of vnet-spoke1.
  • Uses the Ubuntu2204 image alias (Ubuntu 22.04 LTS) and Standard_B1s size (1 vCPU, 1 GiB RAM). B1s is included in the Azure free tier (750 hours/month for 12 months).
  • The --public-ip-address "" flag skips creating a public IP, keeping the VM private-only.
  • The --nsg "" flag skips creating an auto-NSG, so you can attach your own later.

This command takes 1-2 minutes to complete. Wait for the JSON output to appear before continuing.

  • Deploy the second VM in spoke2 by running this command:
az vm create --name vm-spoke2 --vnet-name vnet-spoke2 --subnet snet-spoke2-workload --image Ubuntu2204 --size Standard_B1s --admin-username azureuser --generate-ssh-keys --public-ip-address "" --nsg ""
  • Confirm both VMs are running by checking the JSON output from each command. Look for "powerState": "VM running" in the response.
  • Note the privateIpAddress values in the output. They should be approximately 10.1.1.4 for vm-spoke1 and 10.2.1.4 for vm-spoke2.

VM creation failed?

  • If you see a SkuNotAvailable error, the Standard_B1s size may be temporarily unavailable in your region. Try running az vm list-skus --location eastus --size Standard_B --output table to check availability.
  • If you see an SSH key error, Cloud Shell may already have keys from a previous session. The --generate-ssh-keys flag reuses existing keys if they are present, so this is usually safe to ignore.

Help me troubleshoot my VM creation error in Azure Cloud Shell.

Create and configure an NSG with custom rules

A Network Security Group acts like a stateful firewall for your subnet. You will create one NSG with two rules: one allowing ICMP (ping) between your internal subnets, and one blocking all inbound traffic from the internet.

  • Create the NSG by running this command:
az network nsg create --name nsg-spokes
  • Add a rule to allow ICMP traffic between your internal subnets by running:
az network nsg rule create --nsg-name nsg-spokes --name Allow-ICMP-Inbound --priority 100 --direction Inbound --access Allow --protocol Icmp --source-address-prefixes 10.0.0.0/8 --destination-address-prefixes 10.0.0.0/8
  • Add a rule to deny all inbound traffic from the internet by running:
az network nsg rule create --nsg-name nsg-spokes --name Deny-Internet-Inbound --priority 200 --direction Inbound --access Deny --protocol "*" --source-address-prefixes Internet --destination-address-prefixes "*"

What do these rules do?

NSG rules are evaluated by priority (lowest number wins). Your Allow-ICMP-Inbound rule at priority 100 permits ping traffic between any addresses in the 10.0.0.0/8 range. This covers all three of your VNets.

The Deny-Internet-Inbound rule at priority 200 blocks everything else from the internet. Because 100 is lower than 200, internal ICMP traffic is allowed before the internet deny rule is checked.

  • Associate the NSG with the spoke1 subnet by running:
az network vnet subnet update --vnet-name vnet-spoke1 --name snet-spoke1-workload --nsg nsg-spokes
  • Associate the NSG with the spoke2 subnet by running:
az network vnet subnet update --vnet-name vnet-spoke2 --name snet-spoke2-workload --nsg nsg-spokes
  • Confirm the NSG rules are in place by running:
az network nsg rule list --nsg-name nsg-spokes --output table
  • You should see both Allow-ICMP-Inbound and Deny-Internet-Inbound listed in the output with their correct priorities and protocols.

NSG association failed?

  • Make sure your NSG name matches exactly: nsg-spokes. Names are case-sensitive.
  • If you see a resource not found error, confirm the VNet and subnet names are correct by running az network vnet subnet list --vnet-name vnet-spoke1 --output table.

Help me fix my NSG subnet association error.

Use Network Watcher to verify your NSG rules

Running commands is one thing. Proving they work is another. Network Watcher's NSG diagnostics tool simulates traffic flows and shows you exactly which rule allows or denies a packet.

Why NSG diagnostics instead of IP flow verify?

Network Watcher offers two rule-checking tools. IP flow verify only supports TCP and UDP protocols. NSG diagnostics supports TCP, UDP, and ICMP. Since your Allow-ICMP-Inbound rule uses the ICMP protocol, NSG diagnostics is the correct tool for verifying it.

  • In the Azure Portal search bar at the top, type Network Watcher and select Network Watcher from the results.
  • In the left menu, under Network diagnostic tools, select NSG diagnostics.

First, test that ICMP traffic from spoke2 to spoke1 is allowed.

  • Under Target resource, set Target resource type to Virtual machine.
  • Select vm-spoke1 as the virtual machine.
  • Under Traffic details, set Protocol to ICMP.
  • Set Direction to Inbound.
  • Set Source type to IPv4 address/CIDR.
  • Enter 10.2.1.4 as the source IP (this is the spoke2 VM).
  • Leave Destination IP address as the VM's private IP (10.1.1.4).
  • Click Run NSG diagnostics.

The result should show traffic is allowed by your Allow-ICMP-Inbound rule.

Now test that internet inbound traffic is denied.

  • Change Protocol to TCP.
  • Change the source IP to 8.8.8.8 (an internet address).
  • Enter * for the Destination port.
  • Click Run NSG diagnostics again.

The result should show traffic is denied by your Deny-Internet-Inbound rule.

What did you just prove?

Your first test confirms that internal traffic (ICMP between spoke VMs) passes through the NSG as intended. Your second test confirms that traffic originating from the public internet is blocked.

This is the same pattern enterprises use: allow internal communication between known subnets while denying external access by default.

NSG diagnostics shows unexpected results?

  • Verify you selected the correct VM (vm-spoke1) under the target resource.
  • Double-check that both spoke subnets have the NSG associated by running az network vnet subnet show --vnet-name vnet-spoke1 --name snet-spoke1-workload --query networkSecurityGroup.id.
  • Make sure you entered the correct source IP for each test.

Help me understand why my NSG diagnostics result is unexpected.

Your VMs are deployed, your NSG is enforcing rules, and Network Watcher confirms it all works. Next up, you will create a custom route table to control how spoke-to-spoke traffic flows through the hub.

Create a Custom Route Table

Your NSG rules are controlling which traffic is allowed into your spoke subnets. Now you will control how that traffic actually travels between spokes.

VNet peering is nontransitive. Spoke1 can reach the hub, and spoke2 can reach the hub, but spoke1 cannot reach spoke2 through the hub automatically. A User Defined Route (UDR) table overrides Azure's default routing and forces traffic through a specific next hop, such as a firewall or network virtual appliance in the hub.

In this step, get ready to:
  • Create a route table with a custom route pointing spoke2-bound traffic to the hub.
  • Associate the route table with spoke1's workload subnet.
  • Verify the routing path using Network Watcher Next Hop.
Create a UDR to force spoke-to-spoke traffic through the hub

Without a custom route, Azure's default routing means spoke1 has no path to spoke2 at all (peering is nontransitive). You will create a route table that tells spoke1 to send spoke2-bound traffic to a hub IP address, simulating the pattern of routing through a Network Virtual Appliance (NVA).

  • Create the route table by running this command in Cloud Shell:
az network route-table create --name rt-spoke-to-spoke
  • Add a route that directs all traffic destined for spoke2's address space (10.2.0.0/16) to the hub IP by running:
az network route-table route create --route-table-name rt-spoke-to-spoke --name route-to-spoke2 --address-prefix 10.2.0.0/16 --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.1.4
  • Confirm the route was created by running:
az network route-table route list --route-table-name rt-spoke-to-spoke --output table

You should see one route named route-to-spoke2 with address prefix 10.2.0.0/16 and next hop type VirtualAppliance.

What does this route do?

The --next-hop-type VirtualAppliance setting tells Azure to forward matching traffic to a specific IP address instead of using the default system route. The IP 10.0.1.4 sits in the hub's workload subnet (snet-hub-workload).

In production, this IP would belong to an Azure Firewall or NVA that inspects and forwards traffic. For this project, the route demonstrates the enterprise routing pattern without the cost of a full firewall (~$1.25/hr).

Seeing a 'resource not found' error?

Make sure your Cloud Shell defaults are still set. Run az configure --defaults location=eastus group=rg-hub-spoke if you see errors about missing resource groups.

Help me troubleshoot my route table creation

Associate the route table with spoke1's subnet

A route table does nothing until it is associated with a subnet. Once attached, every VM in that subnet uses the custom routes.

  • Associate the route table with snet-spoke1-workload by running:
az network vnet subnet update --vnet-name vnet-spoke1 --name snet-spoke1-workload --route-table rt-spoke-to-spoke
  • Confirm the association by running:
az network vnet subnet show --vnet-name vnet-spoke1 --name snet-spoke1-workload --query routeTable.id --output tsv

You should see a resource ID string ending in rt-spoke-to-spoke.

Route table not showing up?

Double-check that the VNet name is vnet-spoke1 and the subnet name is snet-spoke1-workload. These are case-sensitive.

Help me fix my route table association

Verify with Network Watcher Next Hop

Network Watcher's Next Hop tool shows you exactly which route Azure uses to forward traffic from a VM to a destination IP. You will use it to confirm your UDR is active.

  • In the Azure Portal search bar at the top, type network watcher and select **Network Watcher** from the results.
  • Under **Network diagnostic tools** in the left navigation, select **Next hop**.
  • Fill in the following fields:
  • **Subscription**: select your Azure subscription.
  • **Resource group**: select rg-hub-spoke.
  • **Virtual machine**: select vm-spoke1.
  • **Network interface**: leave the default.
  • **Source IP address**: enter 10.1.1.4.
  • **Destination IP address**: enter 10.2.1.4 (the spoke2 VM).
  • Click the **Next hop** button to run the test.

The result should show next hop type VirtualAppliance with next hop IP address 10.0.1.4. This proves your UDR is overriding the default routing and directing spoke-to-spoke traffic through the hub.

What do the Next Hop results mean?

Next Hop returns three pieces of information: the next hop type, the next hop IP address, and the route table ID. When the route table ID shows a user-defined route table (instead of "System Route"), you know a custom UDR is in effect.

If you removed the custom route, the next hop type would show VNetPeering because Azure's default system route sends traffic directly across the peering link.

Compare with and without your UDR

To see the difference your route table makes, temporarily remove the association by running:

az network vnet subnet update --vnet-name vnet-spoke1 --name snet-spoke1-workload --remove routeTable

Re-run the Next Hop test in the portal. The next hop type should now show VNetPeering. Then re-associate the route table:

az network vnet subnet update --vnet-name vnet-spoke1 --name snet-spoke1-workload --route-table rt-spoke-to-spoke

Run Next Hop one more time to confirm it shows VirtualAppliance again.

Next Hop still showing VNetPeering?

Make sure you re-associated the route table in the previous tip exercise. Run the subnet show command to confirm the route table is attached.

If you skipped the compare exercise, check that the initial association command completed without errors.

Help me troubleshoot my Next Hop results

Your hub-spoke network is fully wired. You have VNets, peering, NSGs, and now a custom route table directing traffic exactly where you want it. Next up, try the Secret Mission to add DNS name resolution across your spokes.

Secret mission

Add Azure Private DNS for Cross-Spoke Name Resolution

Your hub-spoke network is fully functional, but VMs can only reach each other by IP address. In production, services need friendly names. Configure Azure Private DNS so your spokes can resolve each other by hostname instead of memorizing IPs.

Clean Up Your Resources

Clean Up Your Resources

Your hub-spoke network used two B1s VMs that consume your $200 free credit while running. VNets, peerings, NSGs, route tables, and Private DNS zones are free or negligible in cost.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Cost warning

The two VMs (Standard_B1s) cost approximately $0.01/hr each from your $200 credit. If left running 24/7, they consume about $15/month combined.

If you are done experimenting, choose Pause or Delete below to stop charges immediately.

Resources you used:

  • 2 Linux VMs (vm-spoke1, vm-spoke2) in Standard_B1s size.
  • 3 Virtual Networks (vnet-hub, vnet-spoke1, vnet-spoke2).
  • 4 VNet peering connections.
  • 1 Network Security Group (nsg-spokes).
  • 1 Route table (rt-spoke-to-spoke).
  • 1 Private DNS zone (corp.internal).
  • 1 Resource group (rg-hub-spoke).

Keep everything running

No action needed. Choose this if you are still actively experimenting with your hub-spoke network or plan to extend it further.

  • Your two VMs will continue consuming credit at approximately $0.01/hr each.
  • All other resources (VNets, peerings, NSGs, route tables, Private DNS) incur no meaningful charges.
  • The Cloud Shell storage account (in a separate resource group) persists for future Cloud Shell sessions and costs only a few cents per month.

Pause - I'll come back to this later

Shut down the VMs to stop compute billing while keeping all your resources intact. You can restart them later to pick up where you left off.

  • Deallocate both VMs by running these commands in Cloud Shell:
az vm deallocate --name vm-spoke1
az vm deallocate --name vm-spoke2

What does deallocate do?

Deallocating stops the VM and releases its compute resources so you are no longer billed for them. The VM disks remain intact (costing only a few cents per month) and you can restart with az vm start when you are ready.

  • All other resources (VNets, peerings, NSGs, route tables, Private DNS zone) remain in place at no cost.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes everything in one command because all resources live in a single resource group.

  • Delete the entire resource group by running this command in Cloud Shell:
az group delete --name rg-hub-spoke --yes --no-wait

What does this command do?

This deletes the resource group rg-hub-spoke and every resource inside it: VMs, VNets, peerings, NSGs, route tables, and the Private DNS zone. The --yes flag skips the confirmation prompt. The --no-wait flag returns control immediately while Azure deletes in the background.

  • The Cloud Shell storage account lives in a separate resource group and is not affected. It costs only a few cents per month and is reused by future Cloud Shell sessions.

Nice Work!

Nice Work!

You just built a production-style hub-spoke network architecture in Azure. You designed IP addressing, configured peering, enforced security with NSGs, and controlled routing with UDRs. These are the core skills tested in the AZ-700 exam's heaviest domain.

What you learned:

  • Designed and deployed a 3-VNet hub-spoke topology with non-overlapping /16 CIDR blocks and created bidirectional VNet peering with forwarded traffic enabled.
  • Built Network Security Group rules controlling ICMP and internet traffic, then proved they work using Network Watcher IP flow verify.
  • Created a User Defined Route table forcing spoke-to-spoke traffic through the hub, verified with Network Watcher Next Hop.
  • Secret Mission: Configured Azure Private DNS with autoregistration for cross-spoke name resolution.

Ready to quiz yourself?