Build a Secured Multi-Subnet Network

Deploy a two-subnet Azure VNet with NSGs to isolate web and database tiers.

Introduction

30 Second Summary

Think about how an office building has different floors with locked doors between them. Only authorized people with the right keycard can move between floors. Cloud networks work the same way, using virtual walls and rules to control which servers can talk to each other.

In this project, you will build a secured multi-subnet network on Azure that separates web servers from databases, configure security rules to control traffic flow, and prove those rules work by testing connectivity between virtual machines.

What You'll Build

A two-subnet network topology on Azure that mirrors how real production environments isolate web servers from databases.

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

  • A Virtual Network with two subnets representing a web tier and a database tier.
  • Network Security Group rules that allow traffic from the web subnet to the database subnet while blocking all other inbound traffic.
  • Hands-on experience with core Azure CLI networking commands used in real production environments and tested on the AZ-700 exam.
  • Secret Mission: Add a third management subnet with jump-box access rules so only you can SSH into the network from a specific IP.

Are there any prerequisites?

You need a personal Microsoft account (Outlook, Hotmail, or Live) and a credit or debit card for Azure account verification. You will not be charged.

This project assumes you are on a Windows PC with PowerShell available.

Set Up Your Azure Account and CLI

Before you can build anything on Azure, you need two things: an Azure account (where your resources live) and the Azure CLI (the command-line tool you use to create and manage those resources).

In this step, you will create a free Azure account, install the Azure CLI on Windows, and sign in so your terminal can talk to Azure directly.

In this step, get ready to:
  • Create a free Azure account.
  • Install the Azure CLI on Windows.
  • Sign in to Azure from PowerShell.
Create your free Azure account

Azure's free account gives you a $200 credit for 30 days and 12 months of free-tier services. You will not be charged for this project.

  • Open your browser and navigate to https://azure.microsoft.com/free.
  • Click Try Azure for free.
  • Sign in with your personal Microsoft account (Outlook, Hotmail, or Live).
  • Follow the prompts to verify your identity with a phone number and credit or debit card.
  • Complete the signup process and wait until you see the Azure portal homepage.

Will I be charged?

No. Azure uses your card only for identity verification. You may see a temporary $1 authorization hold that gets reversed automatically.

The free account includes $200 in credit for 30 days and 750 hours per month of B2ats v2 VMs free for 12 months. This project stays well within those limits.

Signup not working?

Make sure you are using a personal Microsoft account, not a work or school account. Prepaid cards are not accepted for verification.

help me troubleshoot my Azure free account signup

Install Azure CLI on Windows

The Azure CLI is a command-line tool that lets you create and manage Azure resources directly from PowerShell. You will use it for every remaining step in this project.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Install the Azure CLI by running this command:
winget install --exact --id Microsoft.AzureCLI

What does this command do?

WinGet is the built-in Windows package manager. The --exact flag ensures it finds the exact package ID Microsoft.AzureCLI rather than a partial match. This downloads and installs the latest Azure CLI version on your machine.

  • Wait for the installation to complete. You will see a progress bar in your terminal.
  • Close PowerShell completely after the install finishes.
  • Reopen PowerShell using the same method (press the Windows key, type PowerShell, press Enter).
  • Verify the installation by running:
az version

You should see version information for the Azure CLI printed in your terminal.

✔️ I see a version number

The Azure CLI is installed and ready. Move on to the next substep.

ⓧ Command not found

This means PowerShell cannot find the az command. Try these fixes:

  • Make sure you closed and reopened PowerShell after the install. The new PATH entries only take effect in a fresh session.
  • If you still see the error, run the install command again. WinGet will either reinstall or confirm it is already installed.
  • As a last resort, download the MSI installer directly from the Azure CLI install page and run it manually.

help me fix Azure CLI not being recognized in PowerShell

Sign in to Azure

Now that the CLI is installed, you need to authenticate it with your Azure account. This lets all future commands run against your subscription.

  • Sign in to Azure by running this command:
az login

What happens when you run this?

A browser window opens automatically asking you to sign in with your Microsoft account. After you authenticate in the browser, your terminal receives a token and displays your subscription details (subscription name and ID).

If the browser does not open automatically, the terminal will display a URL and a device code. Open the URL manually and enter the code to authenticate.

  • Sign in with the same Microsoft account you used to create your Azure free account.
  • Return to PowerShell after signing in. You should see a JSON list containing your subscription name and subscription ID.

Not seeing your subscription?

If the output shows an empty list or an error, confirm that you completed the Azure free account signup fully (you should have seen the Azure portal homepage).

If you have multiple Microsoft accounts, make sure you authenticated in the browser with the same account you used during signup.

help me fix az login not showing my subscription

Your Azure account is active and your CLI is authenticated. Next up, you will create a resource group and build the virtual network that your subnets will live inside.

Create Your Resource Group and Virtual Network

With Azure CLI authenticated, you can start building the network infrastructure for this project.

Every cloud network starts with a foundation: a container to hold your resources, a private network for them to communicate on, and separate segments (subnets) to isolate different tiers. In this step, you'll create all three.

In this step, get ready to:
  • Create a resource group in the eastus region.
  • Create a Virtual Network with a web subnet.
  • Add a database subnet to the Virtual Network.
Create your resource group

Every Azure resource needs a home. A Resource Group is a logical container that holds related resources together, making them easy to manage and delete as a single unit.

  • Create a resource group called rg-network-lab by running this command:
az group create --name rg-network-lab --location eastus
  • You should see JSON output in your terminal that includes "provisioningState": "Succeeded" near the bottom.

What is a resource group?

A resource group is a logical container for Azure resources that share the same lifecycle. When you delete the group, everything inside it goes away too.

The --location eastus flag sets the physical datacenter region where your resource group metadata is stored. Your resources will also default to this region.

Seeing an authorization error?

Make sure you're signed in by running az login first. If you see a location-related error, try replacing eastus with westus2 in the command.

help me fix an authorization error with az group create

Create your Virtual Network and web subnet

A Virtual Network (VNet) is your private, isolated network space in Azure. You give it a large IP address range and then carve out smaller subnets inside it for different purposes.

  • Create a Virtual Network called vnet-lab with a web subnet by running this command:
az network vnet create --resource-group rg-network-lab --name vnet-lab --address-prefixes 10.0.0.0/16 --subnet-name snet-web --subnet-prefixes 10.0.1.0/24
  • You should see JSON output that confirms the Virtual Network was created. Look for "provisioningState": "Succeeded" in the response.

What do these IP addresses mean?

The address ranges use CIDR notation. 10.0.0.0/16 gives your entire network 65,536 possible IP addresses. The /16 means the first 16 bits of the address are fixed.

10.0.1.0/24 carves out 256 of those addresses for the web subnet. The /24 fixes the first 24 bits, leaving room for 256 addresses in that subnet.

Seeing 'ResourceGroupNotFound'?

Double check that you spelled rg-network-lab exactly as shown. The resource group must already exist before you can create resources inside it.

help me fix a ResourceGroupNotFound error when creating a VNet

Add the database subnet

Your web subnet is ready. Now you need a separate subnet for the database tier so you can attach different security rules to each one later.

  • Add a database subnet to your Virtual Network by running this command:
az network vnet subnet create --resource-group rg-network-lab --vnet-name vnet-lab --name snet-database --address-prefixes 10.0.2.0/24
  • You should see JSON output confirming the subnet was created with "provisioningState": "Succeeded".
  • Confirm both subnets exist by running this command:
az network vnet subnet list --resource-group rg-network-lab --vnet-name vnet-lab --output table
  • You should see both snet-web and snet-database listed in the table output.

Why separate subnets?

Putting your web servers and databases in different subnets lets you attach different security rules to each one. In the next step, you'll create rules that allow the web subnet to reach the database subnet while blocking everyone else.

Seeing 'address prefix overlaps'?

Each subnet needs a unique address range within the Virtual Network. Make sure you typed 10.0.2.0/24 for the database subnet. If you accidentally used 10.0.1.0/24, that range is already taken by the web subnet.

help me fix an address prefix overlap error when creating a subnet

Your network foundation is in place with two isolated subnets. Next up, you'll lock them down with Network Security Groups that control exactly which traffic is allowed between them.

Configure Network Security Groups

Your Virtual Network has two subnets, but right now any traffic can flow freely between them. In a production environment, you want your database subnet to accept connections only from trusted sources.

To control this traffic, you will create Network Security Groups (NSGs) and attach them to each subnet. NSGs act as virtual firewalls that filter inbound and outbound traffic based on rules you define.

In this step, get ready to:
  • Create two Network Security Groups for the web and database subnets.
  • Add rules that allow SSH from the web subnet and deny everything else to the database subnet.
  • Associate each NSG with its corresponding subnet.
Create the Network Security Groups

Each subnet gets its own NSG so you can apply different rules to the web tier and the database tier independently.

  • Create the NSG for your web subnet by running this command:
az network nsg create --resource-group rg-network-lab --name nsg-web
  • Create the NSG for your database subnet by running this command:
az network nsg create --resource-group rg-network-lab --name nsg-database

What do these commands do?

Each command creates an empty Network Security Group inside your rg-network-lab resource group. An empty NSG comes with default rules that allow outbound internet access and deny most inbound traffic.

You will add custom rules next to fine-tune what gets allowed in.

  • After each command, you should see a large JSON block in your terminal showing the newly created NSG details, including a provisioningState of Succeeded.

Not seeing the JSON output?

Make sure you typed the resource group name exactly as rg-network-lab. A typo in the group name will cause an error saying the resource group was not found.

Also confirm you are still signed in by running az account show. If your session expired, run az login again.

help me troubleshoot why my az network nsg create command is failing

Add security rules to the database NSG

The database subnet should only accept SSH connections from the web subnet. All other inbound traffic should be blocked.

  • Create a rule that allows SSH traffic from the web subnet by running this command:
az network nsg rule create --resource-group rg-network-lab --nsg-name nsg-database --name AllowSSHFromWeb --protocol Tcp --priority 100 --destination-port-range 22 --access Allow --direction Inbound --source-address-prefix 10.0.1.0/24

What does this rule do?

  • --protocol Tcp targets TCP traffic, which is what SSH uses.
  • --priority 100 makes this rule evaluate first. Lower numbers mean higher priority.
  • --destination-port-range 22 limits the rule to port 22 (the SSH port).
  • --access Allow permits traffic that matches all conditions.
  • --source-address-prefix 10.0.1.0/24 restricts the source to only the web subnet's IP range.
  • You should see JSON output with provisioningState showing Succeeded.
  • Now create a rule that denies all other inbound traffic by running this command:
az network nsg rule create --resource-group rg-network-lab --nsg-name nsg-database --name DenyAllInbound --protocol "*" --priority 4096 --destination-port-range "*" --access Deny --direction Inbound --source-address-prefix "*"

Why a deny-all rule?

Azure's default rules already deny unsolicited inbound traffic from the internet, but they allow traffic from within the virtual network. This explicit deny-all rule at priority 4096 catches anything that did not match the allow rule at priority 100.

The combination means: SSH from the web subnet gets through, everything else is dropped.

  • Verify both rules were created by running this command:
az network nsg rule list --resource-group rg-network-lab --nsg-name nsg-database --output table
  • You should see a table listing AllowSSHFromWeb with priority 100 and DenyAllInbound with priority 4096.

Missing a rule in the table?

Double-check the --nsg-name flag. If you accidentally created the rule on nsg-web instead of nsg-database, delete it and re-run the command with the correct NSG name.

If you see a priority conflict error, make sure each rule within the same NSG has a unique priority number.

help me fix my NSG rules that are not showing up correctly

Associate NSGs with their subnets

An NSG does nothing until it is attached to a subnet or network interface. You will associate each NSG with its matching subnet now.

  • Attach nsg-web to the web subnet by running this command:
az network vnet subnet update --resource-group rg-network-lab --vnet-name vnet-lab --name snet-web --network-security-group nsg-web
  • Attach nsg-database to the database subnet by running this command:
az network vnet subnet update --resource-group rg-network-lab --vnet-name vnet-lab --name snet-database --network-security-group nsg-database

What does subnet update do here?

The --network-security-group flag on az network vnet subnet update binds the named NSG to the subnet. From this point forward, every resource in that subnet is subject to the NSG's rules.

  • After each command, you should see JSON output for the subnet that includes a networkSecurityGroup field containing the NSG's resource ID.

Seeing a 'not found' error?

Make sure --vnet-name is vnet-lab and --name matches the exact subnet name (snet-web or snet-database). These are case-sensitive.

If the NSG name is wrong, you will see a message saying the security group could not be found. Run az network nsg list --resource-group rg-network-lab --output table to confirm the exact NSG names.

help me fix my subnet update command that is failing with a not found error

Your network is now locked down. The database subnet only accepts SSH from the web subnet, and everything else is denied. Next up, you will deploy VMs into each subnet and prove that these rules actually work.

Deploy VMs Into Each Subnet

Your network security rules are locked down. The database subnet only accepts SSH from the web subnet, and everything else is denied.

Now you need actual machines inside those subnets to prove the rules work. In this step, you'll deploy two Linux VMs. One sits in the web subnet with a public IP so you can reach it from your computer. The other sits in the database subnet with no public IP, making it accessible only from within the network.

In this step, get ready to:
  • Deploy a web VM with a public IP into the web subnet.
  • Deploy a database VM with no public IP into the database subnet.
  • Note the IP addresses needed for connectivity testing.
Deploy the web VM into snet-web

The web VM is your entry point into the network. It needs a public IP address so you can SSH into it directly from your local machine.

  • Deploy the web VM by running this command in PowerShell:
az vm create --resource-group rg-network-lab --name vm-web --vnet-name vnet-lab --subnet snet-web --image Ubuntu2204 --size Standard_B2ats_v2 --admin-username azureuser --generate-ssh-keys

What does this command do?

  • --vnet-name vnet-lab --subnet snet-web places the VM inside your web subnet.
  • --image Ubuntu2204 installs Ubuntu 22.04 LTS as the operating system.
  • --size Standard_B2ats_v2 selects the free-tier VM size (750 hours/month free for 12 months).
  • --admin-username azureuser sets the SSH login username to azureuser.
  • --generate-ssh-keys creates an SSH key pair in your ~/.ssh/ folder if one doesn't already exist.

Why no --nsg flag here?

By default, az vm create creates a NIC-level NSG that allows SSH inbound. For the web VM, this is fine because you want to SSH in from your machine.

The subnet-level nsg-web also permits inbound SSH through its default rules.

  • Wait for the command to finish. This takes 1-2 minutes.
  • When it completes, you'll see JSON output. Find the publicIpAddress field and write down its value. You'll need this IP to SSH into the web VM in the next step.

VM creation failed?

If you see an error about the SKU not being available, the Standard_B2ats_v2 size may not be offered in your region. Check availability by running:

az vm list-skus --location eastus --size Standard_B2ats_v2 --output table

If you see a quota error, your free account may have hit its vCPU limit. Try a different region by adding --location westus2 to the command.

help me troubleshoot my az vm create error

Deploy the database VM into snet-database

The database VM sits deeper in the network. It has no public IP, so the only way to reach it is through the web VM inside the same virtual network.

  • Deploy the database VM by running this command:
az vm create --resource-group rg-network-lab --name vm-database --vnet-name vnet-lab --subnet snet-database --public-ip-address "" --nsg "" --image Ubuntu2204 --size Standard_B2ats_v2 --admin-username azureuser --generate-ssh-keys

What does this command do?

  • --public-ip-address "" tells Azure not to assign a public IP. This VM is unreachable from the internet.
  • --nsg "" prevents Azure from creating a default NIC-level NSG. Your subnet-level nsg-database already controls all traffic into this subnet.
  • The remaining flags (image, size, username, SSH keys) match the web VM for consistency.
  • Wait for the command to finish (another 1-2 minutes).
  • In the JSON output, find the privateIpAddress field and write it down. This is the address you'll use to reach the database VM from inside the web subnet.

Why note the private IP?

The database VM has no public IP. The only way to connect to it is by SSHing from the web VM using the private IP address.

Azure assigns private IPs from the subnet's address range (10.0.2.0/24), so your database VM's private IP will be something like 10.0.2.4.

Don't see a private IP in the output?

Make sure the command completed successfully. Look for "provisioningState": "Succeeded" in the JSON output.

If the output scrolled past, you can retrieve the private IP later with az vm show.

help me find my VM's private IP address

Both VMs are running. You have a web VM you can reach from the internet, and a database VM that's only reachable from within the virtual network. Next, you'll SSH in and prove that your NSG rules actually control traffic the way you designed them.

Test Connectivity and Prove Your Rules Work

You have a full network in place with two VMs, two subnets, and custom security rules. Now comes the most important part: proving those rules actually work.

You will run three connectivity tests that together demonstrate your network is properly secured. If SSH works where it should and fails where it should not, your Network Security Group rules are doing their job.

In this step, get ready to:
  • SSH into the web VM from your local machine.
  • SSH from the web VM into the database VM (proving the NSG allows it).
  • Confirm the database VM is unreachable directly from the internet.
SSH into the web VM from your local machine

The web VM has a public IP address, which means you can reach it directly from the internet over SSH. This is your entry point into the network.

  • Connect to the web VM by running this command in PowerShell:
ssh [[VM_USERNAME="your VM admin username"]]@[[VM_WEB_PUBLIC_IP="public IP of vm-web from the previous step"]]

What username should I use?

When you created the VMs with az vm create, Azure set the admin username to your current Windows username by default. If your Windows username is a reserved name (like "admin" or "administrator"), Azure falls back to azureuser.

Check the output from when you created vm-web in the previous step. The adminUsername field tells you exactly which username to use.

What is the fingerprint prompt?

The first time you SSH into a new machine, you will see a message asking if you want to continue connecting. This is SSH verifying the server's identity.

Type yes and press Enter to accept the fingerprint and connect.

After accepting the fingerprint, your terminal prompt changes to show the Linux hostname. You are now inside the web VM.

Connection timed out or refused?

  • Double-check that you copied the correct public IP from the az vm create output for vm-web.
  • Make sure you are using the right username. Check the adminUsername field from the previous step's output.
  • Confirm the VM is still running. In a new PowerShell window, run az vm get-instance-view --resource-group rg-network-lab --name vm-web --query instanceView.statuses and look for a running status.

Help me troubleshoot my SSH connection to the web VM.

SSH from the web VM to the database VM

Now for the real test. Your NSG rule on the database subnet allows SSH (TCP port 22) only from the web subnet's address range (10.0.1.0/24). Since you are inside the web VM (which lives in that subnet), your SSH attempt to the database VM should succeed.

  • From inside the web VM's SSH session, connect to the database VM using its private IP:
ssh [[VM_USERNAME="your VM admin username"]]@[[VM_DATABASE_PRIVATE_IP="private IP of vm-database from the previous step"]]
  • Type yes when prompted to accept the fingerprint.

Your terminal prompt changes again, showing the database VM's hostname. You are now two hops deep: your machine connected to the web VM, and the web VM connected to the database VM.

Why does this prove the NSG works?

The database NSG has a deny-all rule at priority 4096, blocking all inbound traffic by default. The only exception is the allow rule at priority 100 that permits TCP port 22 from 10.0.1.0/24.

Your SSH attempt came from an IP in the 10.0.1.x range (the web subnet), so it matched the allow rule and succeeded. Any traffic from outside that range would hit the deny-all rule and be dropped.

Connection refused from the web VM?

  • Make sure you are using the private IP of vm-database (a 10.0.2.x address), not the public IP of vm-web.
  • Confirm the NSG rule uses --source-address-prefix 10.0.1.0/24 and --destination-port-range 22. A typo in either value blocks the traffic.
  • Check that the NSG is associated with the correct subnet. Run az network vnet subnet show --resource-group rg-network-lab --vnet-name vnet-lab --name snet-database and verify the networkSecurityGroup field references nsg-database.

Help me troubleshoot why I cannot SSH from vm-web to vm-database.

Confirm the database VM is unreachable from the internet

The final proof is showing that the database VM is completely isolated from direct internet access. You deployed it with no public IP address, which means there is no route from the internet to reach it.

  • Exit out of the database VM's SSH session by typing:
exit
  • Exit out of the web VM's SSH session by typing:
exit

You are back on your local machine in PowerShell. Now try to reach the database VM directly.

  • Attempt to SSH to the database VM's private IP from your local machine:
ssh -o ConnectTimeout=5 [[VM_USERNAME="your VM admin username"]]@[[VM_DATABASE_PRIVATE_IP="private IP of vm-database from the previous step"]]

This connection will fail. The private IP address (10.0.2.x) is not routable from the internet, so your machine has no way to reach it.

Why does this prove isolation?

The database VM has no public IP address. Without a public IP, there is no entry point for internet traffic to reach the VM, regardless of what the NSG rules say.

This is defense in depth. The NSG provides rule-based filtering, and the lack of a public IP provides network-level isolation. Together, they ensure the database VM is only reachable from within the VNet (specifically, from the web subnet).

Your network security is proven. You have a working multi-subnet architecture where the web tier can talk to the database tier, but nobody on the internet can reach the database directly. Next up, a secret mission that adds even more security layers.

Secret mission

Add a Management Subnet with Jump-Box Rules

Your network has two subnets and security rules that control traffic between them. But what if you want to control who can access the network in the first place? In this challenge, add a management subnet with a jump box that serves as the only entry point into your entire network.

Clean Up Your Resources

Clean Up Your Resources

This project uses Azure VMs that count toward your free tier hours. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Cost warning

Your VMs use the Azure free tier allowance of 750 hours per month of Standard_B2ats_v2 instances. Two VMs running 24/7 consume about 1,460 hours, which exceeds the 750-hour monthly limit.

If you are done with this project, delete your resources now to avoid unexpected charges. If you want to keep experimenting, deallocate your VMs when you are not using them.

Resources you used:

  • 1 Resource Group (rg-network-lab).
  • 1 Virtual Network (vnet-lab) with 2 subnets (or 3 if you completed the Secret Mission).
  • 2-3 Linux VMs (Standard_B2ats_v2).
  • Network Security Groups.
  • Public IP addresses.

Keep everything running

No action needed. Choose this if you are still actively experimenting with your network and testing connectivity.

  • Your VMs will keep running and consuming free tier hours.
  • Monitor your usage in the Azure portal under Cost Management + Billing to ensure you stay within the 750-hour monthly limit.
  • The Azure CLI remains installed on your machine.

Pause - I'll come back to this later

Deallocate your VMs to stop compute billing while keeping all your resources intact. You can start them again anytime.

  • Deallocate the web VM by running:
az vm deallocate --resource-group rg-network-lab --name vm-web
  • Deallocate the database VM by running:
az vm deallocate --resource-group rg-network-lab --name vm-database

What does deallocating do?

Deallocating releases the compute resources and stops billing for the VM. Your disks, network configuration, and NSG rules all stay in place. When you are ready to resume, run az vm start with the same resource group and VM name.

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 inside the same resource group.

  • Delete the entire resource group and all resources inside it by running:
az group delete --name rg-network-lab --yes --no-wait

What does this command do?

The --yes flag skips the confirmation prompt. The --no-wait flag returns control to your terminal immediately while Azure deletes the resources in the background.

This removes the resource group, Virtual Network, subnets, NSGs, VMs, disks, and public IP addresses. Nothing from this project remains in your Azure account.

  • Verify the resource group is gone by running:
az group list --output table

You should no longer see rg-network-lab in the output. It may take a few minutes for deletion to complete.

Nice Work!

Nice Work!

You just built a production-style network on Azure from scratch. You now have a multi-subnet topology with provably working security rules, built entirely from the command line.

You've learned how to:

  • Build a Virtual Network with multiple subnets representing different application tiers.
  • Configure Network Security Groups with custom inbound rules to control traffic flow between subnets.
  • Deploy VMs into isolated subnets and prove your security rules work by testing connectivity end-to-end.
  • Secret Mission: Added a management subnet with jump-box access rules for defense-in-depth security.

Ready to quiz yourself?