Secure an Azure Virtual Network
Build a three-tier VNet with NSGs and review your security posture.
Introduction
30 Second Summary
Think about how a bank doesn't rely on just one lock. It layers security with vaults, cameras, guards, and access controls so that no single failure compromises everything.
In this project, you will build a three-tier virtual network in Azure and lock each tier down with Network Security Group rules that only allow strictly necessary traffic. You will then assess your network's security posture using Microsoft Defender for Cloud.
What You'll Build
A segmented enterprise-style virtual network architecture secured with layered Network Security Group rules and assessed by Microsoft Defender for Cloud's free Foundational CSPM tier.
By the end of this project, you'll have:
- A virtual network with three subnets for web, app, and database tiers.
- Three Network Security Groups with custom inbound rules enforcing least-privilege traffic flow.
- A Defender for Cloud security assessment showing your Secure Score and recommendations.
- Secret Mission: Add explicit deny-all rules and verify traffic isolation between tiers.
Do I need an Azure subscription?
Yes, you need an Azure account. A free trial or pay-as-you-go subscription works perfectly.
Every resource in this project is free. NSGs and virtual networks with no attached VMs incur zero charges.
Install the Azure CLI and Sign In
Every command you run in this project goes through the Azure CLI. It is the tool that lets you create, configure, and secure Azure resources directly from your terminal.
In this step, you will install the Azure CLI on Windows and sign in to your Azure account. Once authenticated, you will confirm that your subscription is active and ready to use.
In this step, get ready to:
- Install the Azure CLI on Windows.
- Sign in to your Azure account.
- Confirm your active subscription.
Install the Azure CLI on Windows
The Azure CLI is a command-line tool you install on your machine. It sends commands to Azure's servers so you can create and manage cloud resources without clicking through the portal.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
- Check whether the Azure CLI is already installed by running this command:
az version
✔️ I see version 2.87.0 or higher
Your Azure CLI is up to date. Skip ahead to the next substep where you sign in.
ⓧ I see an older version
You have the Azure CLI installed, but it needs an update. The CLI has a built-in upgrade command.
- Upgrade the Azure CLI to the latest version by running this command:
az upgrade
What does this command do?
The az upgrade command updates the Azure CLI and all installed extensions to the latest version. It has been available since version 2.11.0.
- After the upgrade finishes, verify the version by running:
az version
You should see 2.87.0 or higher in the output. Once confirmed, move on to the next substep.
ⓧ Command not found
The Azure CLI is not installed yet. You will install it using winget, the Windows Package Manager.
- Install the Azure CLI by running this command:
winget install --exact --id Microsoft.AzureCLI
What does this command do?
The winget install command downloads and installs packages from the Windows Package Manager repository. The --exact flag ensures you get the official Microsoft Azure CLI package and not a similarly named one.
Windows may ask you to accept license agreements
If prompted with source agreement terms, type Y and press Enter to accept. You may also see a User Account Control elevation prompt. Click Yes to allow the installation.
- Close PowerShell completely and reopen it so that the new az command is available on your path.
- Verify the installation by running:
az version
You should see 2.87.0 or higher in the output.
Still seeing 'command not found'?
- Make sure you closed and reopened PowerShell after the install finished. The az command is not available until a new terminal session starts.
- If you are on Windows 10 and winget itself is not recognized, install App Installer from the Microsoft Store first, then retry.
Ask for help: Why is az not recognized after I installed the Azure CLI with winget?
Sign in to your Azure account
Now that the CLI is installed, you need to authenticate it with your Azure account. This links your terminal to your subscription so that every command runs against your cloud resources.
- Sign in to Azure by running this command:
az login
What happens when you run az login?
The command opens your default web browser and asks you to sign in with your Microsoft account. After you authenticate in the browser, the CLI receives a token and your terminal is ready to manage Azure resources.
If you are on 64-bit Windows, the CLI may also prompt you to select a subscription directly in the terminal. Pick the one you want to use for this project.
- Complete the sign-in in your browser when it opens.
- Return to PowerShell. You should see a JSON list of your subscriptions. The one marked isDefault: true is your active subscription.
- Confirm your subscription details by running:
az account show
You should see output showing your subscription name, ID, and state as Enabled.
Browser didn't open or sign-in failed?
- If the browser did not open automatically, look in your terminal for a URL and a device code. Open the URL manually and enter the code to complete sign-in.
- Make sure you are signing in with the same Microsoft account that owns your Azure subscription.
Ask for help: az login is not recognizing my Azure subscription after I sign in.
Set your default subscription
If you only have one subscription, it is already set as the default. If you have multiple subscriptions, you need to tell the CLI which one to use for this project.
- Check the output from az account show. If the correct subscription is displayed, skip ahead to the validation below.
- If you need to switch to a different subscription, set it by running:
az account set --subscription "[[SUBSCRIPTION_ID="your subscription name or ID"]]"
Where do I find my subscription ID?
The subscription ID was listed in the JSON output when you ran az login. Look for the id field next to the subscription you want to use. You can also use the subscription name instead of the ID.
- Confirm the switch worked by running:
az account show
The output should now display the subscription you selected.
Your CLI is installed and authenticated. Next up, you will create the virtual network and subnets that form the backbone of your secure architecture.
Create the Virtual Network and Subnets
Your Azure CLI is installed and you're signed in. Now it's time to build the network architecture that everything else in this project depends on.
In enterprise environments, putting all resources on a single flat network is a security risk. If an attacker compromises one server, they can reach everything else. You'll solve this by creating a segmented virtual network with separate subnets for each tier.
In this step, get ready to:
- Create a resource group to hold all project resources.
- Build a virtual network with three isolated subnets (web, app, database).
- Verify the subnet layout using the Azure CLI.
Create a resource group
A resource group is a logical container in Azure that holds related resources together. By placing everything in one group, you can manage, monitor, and delete all project resources as a single unit.
- Create a resource group called secure-network-rg in the eastus region by running this command:
az group create --name secure-network-rg --location eastus
What does this command do?
- --name secure-network-rg gives your resource group a descriptive name you'll reference in every command that follows.
- --location eastus places the group in the East US data center. All resources you add to this group will default to the same region.
You should see JSON output with "provisioningState": "Succeeded" confirming the group was created.
Seeing an authorization error?
Make sure you're signed in with an account that has Contributor or Owner permissions on your subscription. Run az account show and confirm your subscription is listed.
If you have multiple subscriptions, set the correct one with az account set --subscription followed by your subscription name or ID.
Help me troubleshoot my az group create error
Create the virtual network and subnets
A VNet is your private network space in Azure. You'll give it a large address block (10.0.0.0/16) and then carve it into three smaller subnets that isolate your web, application, and database tiers from each other.
- Create the virtual network with the first subnet (web tier) by running this command:
az network vnet create --resource-group secure-network-rg --name secure-vnet --address-prefixes 10.0.0.0/16 --subnet-name web-subnet --subnet-prefixes 10.0.1.0/24
What does this command do?
- --address-prefixes 10.0.0.0/16 defines the overall address space. The /16 block gives you 65,536 possible IP addresses to work with.
- --subnet-name web-subnet --subnet-prefixes 10.0.1.0/24 creates the first subnet inside the VNet. The /24 block reserves 256 addresses for the web tier.
You should see JSON output showing the VNet and web subnet were created successfully.
Seeing a resource group not found error?
Double-check that the --resource-group value matches exactly: secure-network-rg. Names are case-sensitive.
Help me fix my az network vnet create error
- Add the remaining two subnets (app tier and database tier) by running these commands:
az network vnet subnet create --resource-group secure-network-rg --vnet-name secure-vnet --name app-subnet --address-prefixes 10.0.2.0/24
az network vnet subnet create --resource-group secure-network-rg --vnet-name secure-vnet --name database-subnet --address-prefixes 10.0.3.0/24
What do these commands do?
Each command adds a new subnet to your existing VNet. The app-subnet gets the 10.0.2.0/24 range and the database-subnet gets 10.0.3.0/24. None of these ranges overlap, so traffic between subnets must be explicitly allowed.
Each command returns JSON output confirming the subnet was created. Look for "provisioningState": "Succeeded" in both responses.
Seeing an overlapping address space error?
Make sure each subnet uses a unique address prefix. If you accidentally reused 10.0.1.0/24 for the app subnet instead of 10.0.2.0/24, Azure will reject the creation. Check your command for typos.
Help me fix my overlapping subnet error
Why three separate subnets?
This is the defense-in-depth principle applied to networking. Each tier gets its own subnet so you can attach different security rules to each one. The web tier might allow public internet traffic, while the database tier only accepts connections from the app tier.
If an attacker compromises a web server, they cannot directly reach the database because the network rules block that path.
Verify your subnet layout
- List all subnets in your VNet by running this command:
az network vnet subnet list --resource-group secure-network-rg --vnet-name secure-vnet --output table
You should see a table listing three subnets: web-subnet, app-subnet, and database-subnet. Each row shows the subnet name and its address prefix.
Only seeing one or two subnets?
Scroll up in your terminal and check the output from the earlier subnet create commands. If either one showed an error, re-run the failed command with the correct values.
Make sure the --vnet-name value is secure-vnet in every command. A typo here creates a subnet in a different (or nonexistent) VNet.
Help me find my missing subnets
Your segmented network is in place. Next up, you'll lock each subnet down with Network Security Group rules that enforce exactly which traffic is allowed between tiers.
Create NSGs and Apply Layered Security Rules
Your three-tier network is in place, but right now traffic can flow freely between all subnets. A compromised web server could reach your database directly.
Network Security Groups act as virtual firewalls attached to each subnet. They evaluate every packet against a priority-ordered list of rules. In this step, you'll create one NSG per tier and lock down traffic so each layer can only communicate with its immediate neighbor.
In this step, get ready to:
- Create three NSGs and associate each one with its subnet.
- Add inbound rules enforcing least-privilege traffic flow between tiers.
- Verify the rules match your intent.
Create the NSGs and associate them with subnets
Each subnet needs its own NSG so you can apply different rules to each tier. You'll create all three NSGs first, then attach each one to its matching subnet.
- Create the three Network Security Groups by running these commands:
az network nsg create --resource-group secure-network-rg --name web-nsg
az network nsg create --resource-group secure-network-rg --name app-nsg
az network nsg create --resource-group secure-network-rg --name database-nsg
What do these commands do?
Each command creates an empty NSG in your resource group. An NSG with no custom rules still has Azure's default rules (allow VNet-to-VNet, allow load balancer, deny all other inbound). You'll add your own allow rules next.
- Associate each NSG with its corresponding subnet by running these commands:
az network vnet subnet update --resource-group secure-network-rg --vnet-name secure-vnet --name web-subnet --network-security-group web-nsg
az network vnet subnet update --resource-group secure-network-rg --vnet-name secure-vnet --name app-subnet --network-security-group app-nsg
az network vnet subnet update --resource-group secure-network-rg --vnet-name secure-vnet --name database-subnet --network-security-group database-nsg
Why associate instead of just create?
An NSG does nothing until it is associated with a subnet or network interface. The --network-security-group flag on az network vnet subnet update links the NSG to the subnet so every resource deployed into that subnet inherits the NSG's rules automatically.
- After each association command completes, you should see JSON output showing the subnet's networkSecurityGroup field now populated with the NSG's resource ID.
Seeing a 'not found' error?
Double check that your NSG names match exactly: web-nsg, app-nsg, database-nsg. Also confirm you're using the resource group name secure-network-rg and VNet name secure-vnet from the previous step.
Help me fix my NSG association error
Add inbound security rules to each NSG
This is where least-privilege networking comes in. Each tier only gets the minimum inbound access it needs to function.
- Add rules to the web NSG to allow public HTTP and HTTPS traffic by running these commands:
az network nsg rule create --resource-group secure-network-rg --nsg-name web-nsg --name AllowHTTP --priority 200 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes '*' --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges 80
az network nsg rule create --resource-group secure-network-rg --nsg-name web-nsg --name AllowHTTPS --priority 201 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes '*' --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges 443
What do these rules do?
The web tier is public-facing, so it needs to accept traffic from anywhere on ports 80 (HTTP) and 443 (HTTPS). The '*' in --source-address-prefixes means any IP address can reach this subnet on those ports.
Priority 200 and 201 are low numbers, which means these rules are evaluated early. Lower priority number equals higher precedence in NSG rule evaluation.
- Add a rule to the app NSG that only allows traffic from the web subnet on port 8080 by running this command:
az network nsg rule create --resource-group secure-network-rg --nsg-name app-nsg --name AllowFromWeb --priority 200 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes 10.0.1.0/24 --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges 8080
Why restrict the source to 10.0.1.0/24?
The address 10.0.1.0/24 is your web subnet's CIDR range. By specifying it as the only allowed source, you ensure that only resources in the web tier can reach the application layer. A random internet user or a compromised resource in another subnet cannot talk to port 8080 directly.
- Add a rule to the database NSG that only allows traffic from the app subnet on port 1433 (SQL) by running this command:
az network nsg rule create --resource-group secure-network-rg --nsg-name database-nsg --name AllowFromApp --priority 200 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes 10.0.2.0/24 --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges 1433
What is defense-in-depth?
You've just implemented defense-in-depth at the network layer. Traffic must pass through the web tier before reaching the app tier, and through the app tier before reaching the database.
Even if an attacker compromises a web server, they cannot jump straight to the database. They would need to also compromise the app tier first, because the database NSG only allows traffic from 10.0.2.0/24 (the app subnet).
Getting a priority conflict error?
Each rule within a single NSG must have a unique priority number. If you accidentally ran a command twice, the second attempt fails because priority 200 is already taken. You can delete the duplicate with az network nsg rule delete and re-create it, or use a different priority number.
Help me fix my NSG rule priority conflict
Verify your security rules
Before moving on, confirm that each NSG has the rules you intended.
- List the rules on all three NSGs by running these commands:
az network nsg rule list --resource-group secure-network-rg --nsg-name web-nsg --output table
az network nsg rule list --resource-group secure-network-rg --nsg-name app-nsg --output table
az network nsg rule list --resource-group secure-network-rg --nsg-name database-nsg --output table
What should I see?
Each command prints a table of custom rules for that NSG. Look for these key columns to confirm your setup:
- web-nsg: two rules (AllowHTTP on port 80, AllowHTTPS on port 443) with source * and access Allow.
- app-nsg: one rule (AllowFromWeb on port 8080) with source 10.0.1.0/24 and access Allow.
- database-nsg: one rule (AllowFromApp on port 1433) with source 10.0.2.0/24 and access Allow.
Not seeing your custom rules?
Make sure you're using the correct --nsg-name value for each command. If the table only shows default rules (with priority 65000+), your custom rule creation may have failed silently. Re-run the az network nsg rule create commands and watch for error messages.
Help me troubleshoot my missing NSG rules
Your network is now locked down with layered security rules. Next up, you'll see how Microsoft Defender for Cloud scores your security posture and recommends improvements.
Review Security Posture in Defender for Cloud
Your three-tier network is locked down with NSG rules that enforce least-privilege traffic flow between subnets. The next question is: how do you prove that your configuration actually meets enterprise security standards?
In this step, you will open Microsoft Defender for Cloud in the Azure Portal and review your network's security posture. Defender for Cloud's Foundational CSPM (Cloud Security Posture Management) is free and enabled by default on every Azure subscription.
In this step, get ready to:
- View your Secure Score on the Defender for Cloud overview page.
- Review security recommendations for your network resources.
- Explore the Asset Inventory to confirm your resources and their security state.
Open Defender for Cloud and view your Secure Score
Every Azure subscription gets the Foundational CSPM plan at no cost. This plan continuously assesses your resources against the Microsoft Cloud Security Benchmark and produces a Secure Score that represents your overall security posture as a single percentage.
- Open your browser and navigate to portal.azure.com.
- Sign in with the same Azure account you used for the CLI.
- In the search bar at the top of the portal, type Microsoft Defender for Cloud and select it from the results.
- The Defender for Cloud overview page opens, showing your Secure Score as a percentage.
What is the Secure Score?
The Secure Score aggregates all findings into a single percentage that indicates your overall security posture. A higher score means lower risk.
Defender for Cloud evaluates your resources against the Microsoft Cloud Security Benchmark. Each recommendation that you resolve improves your score.
Why is the Foundational CSPM free?
Microsoft provides Foundational CSPM at no cost because basic security posture visibility benefits the entire Azure ecosystem. The free tier includes continuous assessments, security recommendations, Secure Score, and compliance checks against the Microsoft Cloud Security Benchmark.
Review recommendations for your network resources
The Recommendations page lists specific actions Defender for Cloud suggests to improve your security posture. It assesses your VNet, subnets, and NSG configuration against the Microsoft Cloud Security Benchmark.
- In the Defender for Cloud left sidebar, click Recommendations.
- Review the list of findings. Look for any recommendations related to your network resources (VNet, subnets, or NSGs).
- Click on any recommendation to see its details, including which resources are affected and how to remediate the finding.
What if I don't see network recommendations?
Defender for Cloud may take a few minutes to assess newly created resources. If you do not see network-related recommendations yet, give it 5-10 minutes and refresh the page.
Your NSG configuration already follows least-privilege principles, so you may see fewer findings than a typical deployment. That is a good sign.
Explore the Asset Inventory
The Asset Inventory gives you a single view of every resource Defender for Cloud monitors, along with each resource's security state.
- In the Defender for Cloud left sidebar, click Inventory.
- In the filter bar, type secure-network-rg to filter down to your resource group.
- Confirm that your VNet, subnets, and NSGs appear in the list. Each resource shows its security state (healthy or unhealthy).
- Click on any resource to see its individual recommendations and alerts.
- Note which recommendations have the highest impact on your Secure Score. These are the findings that would improve your posture the most if resolved.
What does healthy vs unhealthy mean?
Defender for Cloud labels a resource as "healthy" when it complies with all applicable security recommendations. A resource is "unhealthy" when it has outstanding recommendations that need attention.
Even healthy resources may still show informational recommendations. The inventory gives you a complete picture of your resources' security state regardless of their health label.
Don't see your resources in the inventory?
If your resources do not appear, Defender for Cloud may still be discovering them. Wait 5-10 minutes and refresh the page. Make sure the subscription filter at the top of the page includes the subscription where you created secure-network-rg.
Help me find my resources in Defender for Cloud's Asset Inventory.
You have now verified your network's security posture using enterprise-grade tooling. Next up, you will clean up the resources you created during this project.
Secret mission
Add Explicit Deny-All Rules
Your NSGs already have default deny rules at the lowest priority, but explicit deny-all rules make your security intent crystal clear. Add them to every tier and prove you understand how NSG priority ordering works.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project uses only free Azure resources. NSGs and VNets with no attached VMs or gateways incur zero charges. There are no ongoing costs regardless of what you choose.
Resources you used:
- Resource group secure-network-rg (contains all project resources).
- Virtual network secure-vnet with address space 10.0.0.0/16.
- Three subnets: web-subnet, app-subnet, database-subnet.
- Three Network Security Groups: web-nsg, app-nsg, database-nsg (with custom inbound rules and deny-all rules).
Keep everything running
No action needed. Choose this if you want to keep experimenting with your network architecture.
- All resources remain in your secure-network-rg resource group at zero cost.
- You can continue adding rules, testing configurations, or attaching VMs later.
- The Azure CLI remains installed on your machine for future projects.
Pause - I'll come back to this later
Since NSGs and VNets incur no charges, there is nothing to pause. Your resources stay idle at zero cost until you return.
- Leave everything as-is. No running processes to stop.
- When you come back, sign in again with az login and pick up where you left off.
Delete - I don't want to use this again
Remove all project resources and start fresh. Deleting the resource group removes the VNet, subnets, NSGs, and all rules in one command.
- Delete the resource group and all its contents by running:
az group delete --name secure-network-rg --yes
- Verify the deletion by running:
az group list --output table
Confirm that secure-network-rg no longer appears in the output. This may take a few minutes to complete.
- Uninstall the Azure CLI if you no longer need it by running:
winget uninstall Microsoft.AzureCLI
After uninstalling, close and reopen your terminal for the change to take effect.
Nice Work!
Nice Work!
You just built and secured an enterprise-style three-tier network in Azure using defense-in-depth principles. This is the exact architecture pattern that cloud security engineers implement in production environments every day.
You've learned how to:
- Built a segmented virtual network with dedicated web, app, and database subnets using the Azure CLI.
- Created and associated Network Security Groups with layered inbound rules enforcing least-privilege traffic flow between tiers.
- Reviewed your security posture using Microsoft Defender for Cloud and interpreted your Secure Score and security recommendations.
- Secret Mission: Added explicit deny-all rules and understood how NSG priority ordering determines which traffic is allowed.
Ready to quiz yourself?