Skip to main content

Accessing VMs Securely with Azure Bastion

Learn to deploy Azure Bastion so VMs never need RDP or SSH exposed to the internet, while still allowing secure browser-based access.

Overview and What You Will Learn

In this lab, you will deploy Azure Bastion into a VNet, connect to a VM with no public IP address at all through the Azure Portal's browser-based session, and confirm that VM's management port was never exposed to the internet at any point.

Why This Matters in Production

A team opens SSH directly to the internet on a production VM "just for now, I'll close it later," and that exposed port becomes exactly the kind of finding a real compromise starts from - automated scanners find open management ports within hours, not weeks. Azure Bastion removes the temptation entirely, since secure access is available without ever needing to expose the port in the first place.

Core Principles

Azure Bastion is deployed once per VNet and provides secure RDP and SSH access to every VM inside that VNet through the Azure Portal, with the VM's own management port never exposed publicly at all.

◈ DIAGRAM
+------------------------------------------+
| You, in a browser, logged into the Portal |
+------------------------------------------+
|
| (HTTPS, through the Portal)
v
+------------------------------------------+
| Azure Bastion (deployed in its own subnet) |
+------------------------------------------+
|
| (private connection, inside
| Azure's own network)
v
+------------------------------------------+
| Your VM - port 22/3389 never exposed |
| to the public internet at all |
+------------------------------------------+

Bastion requires its own dedicated subnet, specifically named AzureBastionSubnet, within the same VNet as the VMs it will provide access to.

Detailed Step-by-Step Practical Lab

  1. Create a Resource Group and VNet with a subnet for VMs:
Bash
az group create --name rg-bastion-lab-mumbai --location centralindia
az network vnet create \
--resource-group rg-bastion-lab-mumbai \
--name vnet-bastion-lab \
--address-prefix 10.0.0.0/16 \
--subnet-name subnet-vms \
--subnet-prefix 10.0.1.0/24
  1. Add the dedicated Bastion subnet - the name must be exactly AzureBastionSubnet:
Bash
az network vnet subnet create \
--resource-group rg-bastion-lab-mumbai \
--vnet-name vnet-bastion-lab \
--name AzureBastionSubnet \
--address-prefix 10.0.2.0/26
  1. Create a public IP for the Bastion resource itself - this is the only public IP in the entire setup, and it belongs to Bastion, not to any VM:
Bash
az network public-ip create \
--resource-group rg-bastion-lab-mumbai \
--name pip-bastion-lab \
--sku Standard
  1. Deploy Azure Bastion into the dedicated subnet:
Bash
az network bastion create \
--resource-group rg-bastion-lab-mumbai \
--name bastion-lab \
--public-ip-address pip-bastion-lab \
--vnet-name vnet-bastion-lab
  1. Create a VM with no public IP address at all:
Bash
az vm create \
--resource-group rg-bastion-lab-mumbai \
--name vm-private-lab \
--image Ubuntu2204 \
--vnet-name vnet-bastion-lab \
--subnet subnet-vms \
--public-ip-address "" \
--admin-username azureadmin \
--generate-ssh-keys
  1. Confirm the VM genuinely has no public IP:
Bash
az vm show \
--resource-group rg-bastion-lab-mumbai \
--name vm-private-lab \
--show-details \
--query "publicIps" --output tsv
## Expected: empty output - no public IP exists
  1. In the Azure Portal, navigate to the VM and select Connect, then Bastion - this opens a browser-based SSH session using Bastion's connection, with the VM's own SSH port never exposed to the internet at any point.

  2. Clean up:

Bash
az group delete --name rg-bastion-lab-mumbai --yes --no-wait

Production Best Practices & Common Pitfalls

Common Mistake

Deciding to skip Bastion "just for a quick one-time fix" and temporarily opening RDP or SSH to the internet instead. "Just for now" is exactly the situation real compromises come from - the moment that thought occurs is the exact moment to deploy Bastion instead.

Tip

One Bastion deployment serves every VM in the VNet - it is not a per-VM cost or setup. Once deployed, it becomes the standard, default access path for the entire environment going forward.

  • The AzureBastionSubnet name is mandatory and exact. Azure specifically looks for this subnet name when deploying Bastion - a differently named subnet, even with an identical address range, will not work.
  • Bastion access still respects RBAC and NSG rules on the destination VM's subnet. Deploying Bastion does not bypass other access controls - it replaces the need for a public IP and open management port, not the need for proper permissions.

Quick Reference & Troubleshooting Commands

Command Description
az network vnet subnet create --name AzureBastionSubnet Create the required dedicated subnet
az network bastion create Deploy Azure Bastion into a VNet
az vm create --public-ip-address "" Create a VM with no public IP at all
az vm show --query "publicIps" Confirm a VM has no public IP assigned

Explore More in Azure Networking and Traffic Management

All 6 Topics

Frequently Asked Questions

Is Accessing VMs Securely with Azure Bastion free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Accessing VMs Securely with Azure Bastion topic cover?

Learn to deploy Azure Bastion so VMs never need RDP or SSH exposed to the internet, while still allowing secure browser-based access.