When I review Azure tenants, I still regularly find virtual machines with RDP or SSH open to the whole internet. Sometimes it's habit, sometimes convenience, sometimes a leftover default from an old project. Either way, exposed management ports are one of the easiest entry points an attacker can ask for. Now that Azure Bastion exists, there's no good reason to leave port 3389 or 22 exposed.

Azure Bastion comes in four SKUs with very different features

Azure Bastion gives you RDP or SSH access to your VMs without opening any inbound management ports to the public internet. You connect through a managed PaaS service that sits in your virtual network. Access works in the browser, and depending on the SKU you can also use native clients. Microsoft's Azure Bastion documentation gives a good overview.

Since late 2024 there are four SKUs: Developer, Basic, Standard, and Premium. Developer is the newest one and is meant for small, personal or test setups. It's cheap, but it doesn't do much. Basic is the entry-level option most people already know. Standard adds more scale and network segmentation, and Premium adds session recording and auditing on top. Microsoft has an official feature comparison of the Bastion SKUs if you want the details.

If you work in a regulated environment or have compliance requirements, session recording in Premium is genuinely useful, since every keystroke can be captured and reviewed later. Standard and Premium both support native clients, so you can keep using mstsc.exe or whatever SSH tool you prefer. They also offer "shareable session links", which let you hand out secure, time-limited access. That comes in handy for support cases or audits. From what I've seen in projects, being able to delegate access and work with familiar tools is why I treat Standard as the minimum for production.

Tip from the trenches: For any production workload, go with at least the Standard SKU to enable native client support and shareable links. Developer is fine for labs, nothing more.

Just-in-time access opens ports only when someone needs them

In existing environments I often come across just-in-time (JIT) VM access, enabled through Defender for Cloud. With JIT, a management port only opens on request, for a limited time and for specific source IPs, which cuts the attack surface considerably. I've also seen admins set the source to "Any" because it's easier, or ignore JIT entirely. JIT needs Defender for Servers Plan 2. I compare the two plans in detail in my post on Defender for Servers Plan 1 vs. Plan 2, and Microsoft has an official guide to just-in-time VM access.

Resource Graph and Defender reveal exposed management ports

I use two methods to find exposed RDP or SSH ports quickly. One is Defender for Cloud → Recommendations, where you look for "Reduce exposure of management ports". The other is a query in Azure Resource Graph Explorer. Search for "Resource Graph Explorer" in the Azure portal and run this:

resources
| where type == "microsoft.network/networksecuritygroups"
| mv-expand rules = properties.securityRules
| where tolower(rules.properties.destinationPortRange) in ("3389", "22")
| where rules.properties.access =~ "Allow"
| where rules.properties.direction =~ "Inbound"
| where rules.properties.sourceAddressPrefix in ("*", "0.0.0.0/0")
| project nsg = name, port = rules.properties.destinationPortRange, source = rules.properties.sourceAddressPrefix, resourceGroup

You need at least Reader on the subscription. The result lists every NSG that still allows RDP or SSH from the internet, so you get a to-do list in a few seconds.

Entra ID login gives you central control over VM access

One improvement that people often skip is Entra ID (formerly Azure AD) login for Windows and Linux VMs. On Windows you can switch it on directly in the VM configuration. Linux needs a few extra packages and steps. Bastion supports Entra ID login for both, and once it's set up you manage admin access centrally, can apply Conditional Access and no longer depend on static local passwords. In day-to-day operations that makes VM access more secure and easier to audit.

Bastion costs money but also supports connections from the Azure CLI

To be honest about cost: Azure Bastion isn't free, and the bill grows once you need several parallel connections or support for multiple vNETs. Developer is affordable, but I'd keep it for single users or test setups. Basic, Standard and Premium in particular are priced for business use, and data transfer is charged on top (the Azure pricing page has the details). Compared with the cost of a security incident or a compliance problem, though, most enterprises will find it a good deal.

Scripting and CLI access are straightforward. This starts an RDP session from your terminal:

az network bastion rdp \
  --name <BastionName> \
  --resource-group <ResourceGroup> \
  --target-resource-id <VMResourceId>

You need Azure CLI v2.0.83 or later and at least the "Virtual Machine User Login" role on the VM.

Bastion and JIT together remove the need for open ports

In 2024 I can't think of a valid reason to leave RDP or SSH open on Azure VMs. Azure Bastion with a suitable SKU, combined with JIT access, handles every situation I run into in practice, from routine admin work to the occasional support case. Which SKU fits depends on your security and compliance requirements and your budget, but for most organizations Standard or Premium is now the realistic baseline. The diagram below shows how the components relate to each other:

flowchart TD
  User["Admin"] --> GUI["Azure Portal/CLI"]
  GUI --> Bastion["Azure Bastion"]
  Bastion --> VM["VM in vNET"]
  Defender["Defender for Cloud JIT"] -- "optional port opening" --> VM
  Entra["Entra ID login"] -- "optional auth" --> Bastion

Figure: Secure VM admin access via Bastion, JIT, and Entra ID

If you still have open management ports, close them now. With Bastion, the secure option is also the easy one.