Deploy and Secure a Linux VM with Azure CLI
Deploy a Linux VM on Azure CLI, configure networking, and serve a live Nginx website.
Introduction
30 Second Summary
Every website you visit lives on a server somewhere. Cloud engineers are the people who create those servers, connect them to the internet, and lock them down so only the right traffic gets through.
In this project, you will deploy a Linux virtual machine in Azure using only the command line, install a web server, and secure it with firewall rules.
What You'll Build
A Linux virtual machine running in Azure with a custom virtual network, firewall rules, and a live Nginx web server accessible from the public internet.
By the end of this project, you'll have:
- A resource group and virtual network created entirely from the Azure CLI.
- A running Linux VM with a public IP address serving a live web page.
- A Network Security Group with custom inbound rules controlling who can access your server.
- Secret Mission: Lock down SSH access so only your IP address can connect.
Are there any prerequisites?
You need an Azure free tier account and the Azure CLI installed on Windows. If you don't have these yet, Step 1 will walk you through verifying your setup.
Verify Azure CLI and Log In
Before you can deploy anything in Azure, your local machine needs a working connection to your Azure account. The Azure CLI is the tool that makes this possible, letting you create and manage cloud resources directly from your terminal.
In this step, you'll confirm the CLI is ready, authenticate with your Azure account, and point your terminal at the right subscription. Once this is done, every command you run will target your free tier resources.
In this step, get ready to:
- Confirm Azure CLI is installed and up to date.
- Log in to your Azure account.
- Set your active subscription.
Open PowerShell and check your Azure CLI version
The Azure CLI needs to be working before you can use it to build infrastructure. A quick version check confirms it's installed and accessible from your terminal.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
- Check your Azure CLI version by running this command:
az version
What should I see?
You should see a JSON output with an azure-cli field showing a version number like 2.87.0. The exact number may differ depending on when you installed the CLI.
Keep your CLI current
If your version is significantly older than 2.87.0, you can update it by running:
az upgrade
Not seeing a version number?
If PowerShell shows an error like "az is not recognized", the Azure CLI may not be in your system PATH. Close and reopen PowerShell, then try again. If it still fails, reinstall the Azure CLI using:
winget install --exact --id Microsoft.AzureCLI
help me troubleshoot why az version is not working in PowerShell
Log in to your Azure account
Now that the CLI is confirmed working, you need to authenticate so Azure knows who is running commands. The login process opens your web browser for secure sign-in.
- Log in to Azure by running this command:
az login
What happens when you run this?
A browser window opens and asks you to sign in with your Microsoft account. After you authenticate, the browser shows a success message and you can close it.
Back in PowerShell, the terminal displays a JSON list of your subscriptions. Look for your free tier subscription in this list.
- After the browser confirms successful login, return to PowerShell.
- Confirm you see a JSON list containing your subscription name and state showing Enabled.
Browser didn't open?
If the browser does not open automatically, look in your terminal for a URL and a device code. Copy the URL into your browser manually and enter the code when prompted.
help me troubleshoot az login not opening a browser window
Set your active subscription
If you have multiple subscriptions, Azure needs to know which one to target. Setting the active subscription ensures all your commands go to the right place.
- Set your free tier subscription as active by running this command:
az account set --subscription "[[SUBSCRIPTION_NAME="your subscription name"]]"
Where do I find my subscription name?
Your subscription name appeared in the JSON output from az login. Look for the name field. Common names for free tier subscriptions include "Azure subscription 1" or "Free Trial".
- Verify you're on the correct subscription by running:
az account show
- Confirm the output shows your subscription name, id, and state as Enabled.
Seeing the wrong subscription?
If az account show displays a different subscription than expected, list all available subscriptions with:
az account list --output table
Find the correct name in the table, then re-run az account set with that name.
help me set the correct Azure subscription as active
Your terminal is now authenticated and pointing at the right subscription. Next up, you'll create a resource group and virtual network to house your Linux VM.
Create a Resource Group and Virtual Network
Your Azure account is logged in and your free tier subscription is active. Now it's time to build the cloud infrastructure that your virtual machine will live inside.
Every Azure deployment starts with two foundational pieces: a resource group to keep your resources organized, and a virtual network to give them a private address space. You'll create both from PowerShell using the Azure CLI.
In this step, get ready to:
- Create a resource group to hold all project resources.
- Build a virtual network with a subnet for your VM.
- Verify each resource exists using Azure CLI queries.
Create a resource group
A Resource Group is a logical container that holds all the Azure resources for a project. Think of it like a folder that groups everything together so you can manage, monitor, and delete resources as a unit.
- Create a resource group named myLabRG in the eastus region by running this command:
az group create --name myLabRG --location eastus
What does this command do?
- az group create tells Azure to create a new resource group.
- --name myLabRG sets the name you'll reference throughout this project.
- --location eastus places the resource group in the East US data centre region.
- Verify the resource group exists by running:
az group list --output table
You should see myLabRG listed in the table with a location of eastus.
Don't see your resource group?
- Check that you typed the command exactly as shown. A typo in --name or --location can cause a silent failure.
- Make sure your subscription is set correctly by running az account show and confirming the state is Enabled.
- Help me troubleshoot my resource group creation.
Create a virtual network with a subnet
A Virtual Network gives your cloud resources a private network to communicate on, just like a physical server needs a LAN. A Subnet divides that network into smaller segments so you can control which resources share the same address range.
- Create a virtual network named myLabVNet with a subnet named myLabSubnet by running this command:
az network vnet create --resource-group myLabRG --name myLabVNet --address-prefix 10.0.0.0/16 --subnet-name myLabSubnet --subnet-prefix 10.0.0.0/24
What does this command do?
- --resource-group myLabRG places the VNet inside the resource group you just created.
- --address-prefix 10.0.0.0/16 defines the overall network address space (65,536 possible addresses).
- --subnet-name myLabSubnet creates a subnet inside the VNet in the same command.
- --subnet-prefix 10.0.0.0/24 carves out 256 addresses for that subnet. Your VM will get an IP from this range.
Why create the VNet explicitly?
Azure can auto-create networking when you deploy a VM. By creating the VNet yourself, you learn exactly how the network layer works.
This matters in real sysadmin roles where you design network architecture, control IP ranges, and segment workloads across subnets.
- Verify the virtual network was created by running:
az network vnet show --resource-group myLabRG --name myLabVNet --output table
You should see myLabVNet with an address space of 10.0.0.0/16.
- Verify the subnet exists by running:
az network vnet subnet list --resource-group myLabRG --vnet-name myLabVNet --output table
You should see myLabSubnet with a prefix of 10.0.0.0/24.
VNet or subnet not showing up?
- Double-check that --resource-group myLabRG matches the exact name you used when creating the resource group (names are case-sensitive).
- If you see an error about the resource group not being found, re-run az group list --output table to confirm it exists.
- Help me troubleshoot my virtual network creation.
Your network foundation is in place. Next up, you'll deploy a Linux virtual machine into this VNet and get a public IP address for it.
Deploy a Linux Virtual Machine
Your network foundation is ready. You have a resource group, a virtual network, and a subnet all wired up and waiting for a workload.
Now it's time to deploy the star of the show: a Linux virtual machine. You'll launch an Ubuntu VM directly into the subnet you created, with a public IP so you can reach it from anywhere.
In this step, get ready to:
- Deploy a Linux VM into your custom virtual network.
- Note the public IP address assigned to the VM.
- Verify the VM is running successfully.
Deploy the VM into your virtual network
This single command creates the VM, generates SSH keys for secure access, assigns a public IP, and auto-creates a Network Security Group that allows SSH connections.
- Deploy your VM by running this command in PowerShell:
az vm create --resource-group myLabRG --name myLabVM --image Ubuntu2204 --size Standard_B2ats_v2 --vnet-name myLabVNet --subnet myLabSubnet --admin-username azureuser --generate-ssh-keys --public-ip-sku Standard
What does this command do?
- --image Ubuntu2204 selects Ubuntu 22.04 LTS as the operating system.
- --size Standard_B2ats_v2 picks the AMD-based burstable VM that qualifies for the Azure free tier (750 hours/month for 12 months).
- --vnet-name myLabVNet --subnet myLabSubnet places the VM inside the network you created in the previous step.
- --generate-ssh-keys creates a key pair at %USERPROFILE%\.ssh\ so you can connect securely without a password.
- --public-ip-sku Standard assigns a Standard SKU public IP address so the VM is reachable from the internet.
How long will this take?
Azure typically provisions a VM in 1-2 minutes. You'll see a blinking cursor while it works. Don't close the terminal or press Ctrl+C.
When the command finishes, you'll see JSON output containing details about your new VM.
Seeing an error about the image or size?
If you see an error mentioning the image alias, try replacing Ubuntu2204 with the full URN: Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest.
If the size is unavailable in your region, try Standard_B2pts_v2 (Arm-based) instead. Both qualify for the free tier.
Help me troubleshoot my az vm create error
Note your public IP address
In the JSON output, look for the publicIpAddress field. This is the address you'll use to connect to your VM from your local machine. Write it down or keep it visible in your terminal.
- If the JSON output has scrolled away, retrieve your public IP by running:
az vm show --resource-group myLabRG --name myLabVM --show-details --query publicIps --output tsv
What if publicIpAddress is empty in the output?
Occasionally, the IP allocation takes a moment. Run the retrieval command above and it will return the assigned IP once it's ready.
Verify the VM is running
- Confirm your VM is up and running by checking its status:
az vm get-instance-view --resource-group myLabRG --name myLabVM --query "instanceView.statuses[1].displayStatus" --output tsv
- You should see VM running printed in your terminal.
Not seeing 'VM running'?
If you see VM starting instead, wait 30 seconds and run the command again. The VM may still be booting.
If you see an error about the resource not being found, double-check the resource group name (myLabRG) and VM name (myLabVM) match exactly what you used in the create command.
Help me figure out why my VM isn't running
Keep an eye on free tier hours
Your VM is covered by the Azure free tier (750 hours/month for 12 months). Running it 24/7 uses about 730 hours/month, which fits within the limit.
To be safe, deallocate the VM when you're not using it:
az vm deallocate --resource-group myLabRG --name myLabVM
Your Linux VM is live and reachable. Next up, you'll SSH into it and install a web server that serves real traffic to the internet.
SSH Into the VM and Install Nginx
Your VM is deployed and running in Azure, but right now it is just an empty Linux box. To make it useful, you need to connect to it and install software.
You'll use SSH to open a remote terminal session on the VM. Once inside, you'll install Nginx, a lightweight web server that will serve a live web page from your VM.
In this step, get ready to:
- Connect to your Linux VM using SSH.
- Install the Nginx web server.
- Verify Nginx is serving its default page locally.
Connect to your VM via SSH
SSH lets you open a terminal session on a remote machine as if you were sitting right in front of it. Your VM already allows SSH connections on port 22 thanks to the Network Security Group rule that was auto-created during deployment.
- Connect to your VM by running this command in PowerShell:
ssh azureuser@[[PUBLIC_IP="your VM's public IP address"]]
What happens when you run this?
On first connection, SSH will display the server's fingerprint and ask you to confirm. Type yes and press Enter to continue.
After authentication, your terminal prompt changes from the PowerShell prompt to something like azureuser@myLabVM:~$. This confirms you are now inside the Ubuntu VM.
- Type yes when prompted to accept the host fingerprint, then press Enter.
- Confirm your prompt now shows azureuser@myLabVM:~$ instead of your local PowerShell prompt.
Can't connect via SSH?
- Double-check that you're using the correct public IP from the previous step. Retrieve it again with:
- If you see "Permission denied (publickey)", your SSH key may not be in the default location. Try specifying it explicitly:
az vm show --resource-group myLabRG --name myLabVM --show-details --query publicIps --output tsv
ssh -i %USERPROFILE%\.ssh\id_rsa azureuser@[[PUBLIC_IP="your VM's public IP address"]]
Why can't I SSH into my Azure VM?
Update packages and install Nginx
Before installing software on Ubuntu, you need to refresh the package list so the system knows about the latest available versions.
- Install Nginx by running this command inside your SSH session:
sudo apt update && sudo apt install nginx -y
What does this command do?
- sudo apt update refreshes Ubuntu's list of available packages so you get the latest version.
- sudo apt install nginx -y installs the Nginx web server. The -y flag auto-confirms the installation prompt so you don't have to type yes manually.
The command will download and install several packages. When it finishes, your terminal returns to the azureuser@myLabVM:~$ prompt.
Seeing 'Unable to locate package nginx'?
- Make sure sudo apt update ran successfully before the install. If it failed, your VM may not have internet access. Confirm the VM's NSG allows outbound traffic (it does by default).
- Check that you are inside the SSH session (your prompt should show azureuser@myLabVM), not in your local PowerShell.
Why is my apt install failing on my Azure VM?
Verify Nginx is running and exit the SSH session
Nginx starts automatically after installation on Ubuntu. You can confirm it is serving its default page by making a request to the server from inside the VM itself.
- Verify Nginx is running locally by running:
curl localhost
You should see a block of HTML output containing Welcome to nginx! in the response. This confirms Nginx is running and serving web pages on port 80 inside the VM.
Why can't I see this page in my browser yet?
Nginx is listening on port 80 inside the VM, but the Network Security Group currently only allows SSH traffic (port 22) from the outside. In the next step, you'll add a rule to open port 80 so the page is accessible from your browser.
- Exit the SSH session by running:
exit
Your terminal prompt returns to your local PowerShell session.
Don't see the Welcome to nginx HTML?
- If curl returns "Connection refused", Nginx may not have started. Run sudo systemctl start nginx and try curl localhost again.
- If you see "command not found" for curl, run sudo apt install curl -y first.
Why isn't Nginx responding on my Azure VM?
Nginx is installed and serving pages locally on your VM. Next up, you'll open port 80 in the Network Security Group so the rest of the internet can reach your web server.
Open HTTP Port and Verify Your Live Website
Nginx is running inside your VM, but the outside world still cannot reach it. The Network Security Group attached to your VM acts as a firewall that blocks all inbound traffic unless you create an explicit rule to allow it.
In this step, you'll open port 80 in the NSG so that HTTP traffic from the internet can reach Nginx. Then you'll confirm the website is live by loading it in your own browser.
In this step, get ready to:
- Find the name of your auto-created Network Security Group.
- Add an inbound rule to allow HTTP traffic on port 80.
- Verify your live Nginx website in a browser.
Find the name of your Network Security Group
When you created the VM, Azure automatically generated an NSG and attached it to the network interface. You need its exact name before you can add rules to it.
- List the NSGs in your resource group by running this command:
az network nsg list --resource-group myLabRG --query "[].name" --output tsv
What does this command do?
The --query "[].name" part uses JMESPath to extract just the NSG names from the JSON response. The --output tsv flag returns plain text instead of formatted JSON, making it easy to read.
You should see a single name in the output, such as myLabVMNSG. Note this name down for the next command.
Don't see any NSG listed?
Make sure you spelled the resource group name exactly as myLabRG (it is case-sensitive). Also confirm the VM was created successfully in a previous step by running az vm list --resource-group myLabRG --output table.
Help me troubleshoot why my NSG list command returns empty results.
Add an HTTP inbound rule to the NSG
Right now the NSG only allows SSH on port 22. Web browsers use port 80 for HTTP, so you need to add a rule that explicitly permits inbound TCP traffic on that port.
- Create an inbound rule named AllowHTTP by running this command:
az network nsg rule create --resource-group myLabRG --nsg-name [[NSGNAME="your NSG name e.g. myLabVMNSG"]] --name AllowHTTP --priority 1001 --direction Inbound --access Allow --protocol Tcp --source-address-prefixes "*" --destination-port-ranges 80
What does this rule do?
This rule tells Azure to allow any IP address (*) to send TCP traffic to port 80 on your VM. The priority 1001 determines evaluation order. Lower numbers are evaluated first, so this rule sits just after the default SSH rule.
NSGs are stateful, meaning that once an inbound request is allowed, the response traffic is automatically permitted without needing a separate outbound rule.
- Verify the rule was created by running:
az network nsg rule list --resource-group myLabRG --nsg-name [[NSGNAME="your NSG name e.g. myLabVMNSG"]] --output table
You should see AllowHTTP listed with access Allow, direction Inbound, and destination port 80.
Seeing an error about the NSG name?
Double check that the --nsg-name value matches the exact name you noted from the previous command. Azure resource names are case-sensitive.
Help me fix an error when creating my NSG rule.
Verify your website in the browser
The firewall is now open. Traffic from the public internet can reach port 80 on your VM, where Nginx is waiting to serve its default page.
- Open a web browser on your local machine (Chrome, Edge, Firefox, or any browser).
- In the address bar, type http:// followed by the public IP address you noted in the previous step, then press Enter.
You should see the Nginx welcome page with a bold heading that reads "Welcome to nginx!" served live from your Azure VM.
What am I looking at?
This page is served by the Nginx process running inside your Linux VM in Azure. The request traveled from your browser, through the public internet, hit the public IP attached to your VM, passed through the NSG (which now allows port 80), and reached Nginx.
You just proved you can deploy, secure, and serve a public website from cloud infrastructure using only the command line.
Page not loading in your browser?
- Confirm you are using http:// (not https://). Port 443 for HTTPS is not open.
- Verify your VM is still running by executing az vm get-instance-view --resource-group myLabRG --name myLabVM --query "instanceView.statuses[1].displayStatus" --output tsv in PowerShell. It should output VM running.
- Check that you are using the correct public IP. Retrieve it again with az vm show --resource-group myLabRG --name myLabVM --show-details --query publicIps --output tsv.
Help me troubleshoot why I cannot load my Nginx page in the browser.
Secret mission
Lock Down SSH to Your IP Only
Right now, your VM accepts SSH connections from any IP address on the internet. That is a real security risk in production. In this secret mission, you will restrict SSH access so that only your personal IP address can connect on port 22.
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.
Your VM compute and disk are covered by the Azure free tier (750 hours/month for 12 months). However, the Standard SKU public IP address costs $0.005/hour as long as it exists, even when the VM is deallocated.
Cost warning
The Standard SKU public IP is billed at $0.005/hour whether the VM is running or not. This is roughly $3.60/month if left allocated.
If you are done with this project, choose Delete below to remove all resources and stop all charges. If you plan to return, Pause deallocates the VM but the public IP cost continues until you delete the resource group.
Resources you used:
- Resource Group: myLabRG (free).
- Virtual Network: myLabVNet (free).
- Virtual Machine: myLabVM (free tier compute).
- OS Disk: P6 SSD (free tier).
- Public IP Address: Standard SKU ($0.005/hour while allocated).
- Network Security Group: myLabVMNSG (free).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your VM.
- Your VM uses free tier hours (750 hours/month). Running it 24/7 uses about 730 hours, which fits within the limit.
- Monitor your usage in the Azure portal under Cost Management.
- The Standard SKU public IP costs $0.005/hour as long as it exists.
Pause - I'll come back to this later
Shut down the VM to stop compute charges while keeping your setup intact. You can restart it later.
- Deallocate the VM by running:
az vm deallocate --resource-group myLabRG --name myLabVM
- This stops compute billing. Your disk, network, and configuration remain intact.
- The public IP address continues to cost $0.005/hour even while the VM is deallocated.
- When you are ready to return, restart the VM by running:
az vm start --resource-group myLabRG --name myLabVM
Delete - I don't want to use this again
Remove all project resources in one command. This is irreversible.
- Delete the entire resource group (which contains all project resources) by running:
az group delete --name myLabRG --yes --no-wait
- This removes the VM, disk, virtual network, subnet, public IP, network security group, and network interface.
- The --no-wait flag returns control to your terminal immediately while Azure deletes the resources in the background.
- Verify the resource group is gone by running:
az group list --output table
- You should no longer see myLabRG in the output. It may take a minute or two to fully disappear.
Nice Work!
Nice Work!
You just deployed and secured a Linux virtual machine in Azure, entirely from the command line. That is the exact workflow cloud engineers and sysadmins use every day to provision production infrastructure.
You learned how to:
- Deploy a Linux VM into a custom virtual network and subnet using the Azure CLI.
- Configure Network Security Group rules to control inbound traffic and open specific ports.
- Install and serve a public Nginx website accessible from anywhere on the internet.
- Lock down SSH access to your specific IP address only, applying the principle of least privilege (Secret Mission).
Ready to quiz yourself?