How to Troubleshoot Azure VM RDP Connection Issues: NSG, Firewall, Public IP, and Bastion Fixes
"Remote Desktop can't connect to the remote computer" is Windows' way of saying it has no idea which of six possible layers actually failed. The NSG, a route table, the guest OS firewall, the public IP, the RDP service itself, or the VM's own boot state can each produce the exact same unhelpful message — which is why guessing wastes more time than working the stack in order, top to bottom, with the tools built specifically to answer each layer's question directly.
# The generic client-side error - identical regardless of root cause:
"Remote Desktop can't connect to the remote computer for one of
these reasons:
1) Remote access to the server is not enabled
2) The remote computer is turned off
3) The remote computer is not available on the network"
# Documented symptom variants that all trigger this same investigation,
# per Microsoft's own RDP-NSG troubleshooting reference:
- RDP connection times out or is refused
- RDP works from one source IP but not another
- RDP stops working immediately after an NSG rule change
- A brand-new VM is unreachable via RDP right after deployment
- Azure Bastion itself returns a generic "Connection failed" with
no further detail
# The unhelpful part: NONE of these symptoms, by themselves, tell you
# which of the following actually failed:
# Public IP / DNS -> NSG (subnet, NIC, OR security admin rules)
# -> Custom route table (UDR) -> Guest OS firewall -> RDP
# service (TermService) -> VM boot / provisioning state
# Guessing which layer is broken wastes time. Working the stack in
# order, with the specific diagnostic tool built for each layer,
# does not.Symptom: RDP connection attempts to an Azure VM fail, time out, or are refused, with an error message that gives no indication of which specific layer — network, guest OS, or VM state — is responsible. Failure point: RDP connectivity depends on a chain of independent layers, any one of which can silently block the connection while producing an identical, generic client-side error. Default platform behaviour: Azure gives you purpose-built diagnostic tools for nearly every layer in this chain — Network Watcher's IP Flow Verify and NSG Diagnostics, boot diagnostics, serial console, Run Command — but none of them run automatically. Diagnosis requires deliberately working through the stack.
Every RDP connection to an Azure VM has to survive six independent checkpoints in sequence, and a failure at any single one produces the exact same generic client-side error — which is precisely why guessing at the cause wastes far more time than methodically working the stack from the outside in. A public IP has to exist and resolve. An NSG — now potentially three separate rule sets, not just one — has to allow the traffic. A route table, if customized, has to actually send the packet toward the VM rather than somewhere else. The guest operating system's own firewall has to permit the connection. The Remote Desktop service itself has to be running and listening. And the VM has to actually be powered on and healthy enough to accept connections at all. This guide walks each layer in the order that produces the fastest diagnosis, with the specific Azure tool built to answer that layer's question directly, and ends with the fix most organizations should be moving toward regardless: Azure Bastion, which removes an entire category of these failure points by design.
Microsoft's own RDP-NSG troubleshooting reference lists several distinct symptoms that all point back to the same investigation: connection timeouts, connection refusals, RDP working from one source IP but not another, RDP breaking immediately after an unrelated NSG change, and a freshly-deployed VM being unreachable from the start. None of these symptom variants, on their own, tells you which layer actually failed — a timeout can mean a blocked NSG rule, a misrouted UDR, or a VM that's still booting, and the client gives you no way to distinguish between them from the error message alone.
| Symptom pattern | What it suggests investigating first |
|---|---|
| Works from one IP, not another | An NSG rule scoped to a specific source IP or range — check rule source scoping |
| Broke immediately after an NSG change | The change itself — review the specific rule modified, check for priority conflicts |
| New VM, never worked | Missing inbound rule entirely, or no public IP assigned at deployment |
| Worked before, now times out with no recent changes | VM power state, guest OS firewall drift, or a platform-level issue — check boot diagnostics first |
These patterns are useful hints, not diagnoses — they narrow where to look first, but the actual confirmation always comes from running the specific tool for that layer, not from pattern-matching the symptom alone.
Before touching any firewall or NSG configuration, confirm the most basic prerequisite: does a network path to the VM exist at all from where you're connecting?
If the VM has no public IP and you're not connected via VPN, ExpressRoute, or peering to the VM's virtual network, an internet-based RDP client has no possible path to reach it — this isn't a misconfiguration to fix at the NSG or firewall layer, it's a fundamental connectivity gap. The fix is either assigning a public IP (with the security trade-offs that implies), establishing private connectivity, or — the better modern answer covered in Section 8 — using Azure Bastion, which requires no public IP on the VM at all.
If a public IP exists and DNS resolves correctly, but RDP still fails, test connectivity from another VM inside the same virtual network before assuming the problem is Azure-side. This isolates whether the issue is specific to your client's network path (a corporate firewall blocking outbound 3389, for instance) versus something genuinely wrong with the target VM's configuration — a distinction that changes where the rest of this guide's diagnosis should focus.
This is where most RDP failures actually live, and it's also where the diagnosis has gotten more layered than many existing runbooks account for. Traffic to a VM's RDP port can now be blocked at up to three distinct rule layers, evaluated in a specific order.
| Rule layer | Where it's managed | Evaluated |
|---|---|---|
| Security admin rules | Azure Virtual Network Manager (organization-wide, if deployed) | First — before any NSG |
| Subnet NSG | Attached to the VM's subnet | Second |
| NIC NSG | Attached directly to the VM's network interface | Third — combined with subnet NSG into "effective" rules |
Common, documented causes of an RDP block at this layer: a missing inbound Allow rule for TCP 3389 entirely, a Deny rule with a lower priority number (higher precedence) than an Allow rule for the same traffic, and — a subtle, frequently-missed one — a conflict between subnet-level and NIC-level NSGs, where one allows the traffic and the other denies it, and only the combined "effective" rule set determines the actual outcome.
Because subnet and NIC NSGs combine, and because Azure Virtual Network Manager's security admin rules (if your organization has deployed AVNM) are evaluated before either, the rule you're looking at in the portal may not be the rule actually determining the outcome. Azure's "Effective security rules" view for a NIC shows the fully combined, actually-enforced rule set — this is the source of truth, not any single NSG's rule list in isolation.
Rather than manually reading through every rule across every layer, Network Watcher provides two purpose-built tools that answer the question directly: is this specific traffic allowed or denied, and which rule is responsible.
IP Flow Verify — fastest for a single, specific traffic test
NSG Diagnostics — checks all three rule layers together, including AVNM
For environments using Azure Virtual Network Manager, NSG Diagnostics is the more complete tool: it explicitly checks security admin rules (labeled GlobalRules in its output), the subnet NSG, and effectively the NIC NSG together in a single pass — surfacing exactly which of the three layers is responsible for a block, something IP Flow Verify alone won't distinguish when multiple layers are in play.
IP Flow Verify and NSG Diagnostics only answer the NSG question — they confirm whether network-layer traffic is permitted to reach the VM's network interface. A clean "Allowed" result definitively rules out NSGs (and, via NSG Diagnostics, security admin rules) as the cause, and means the investigation needs to move to Sections 5 through 7: route tables, the guest OS firewall, the RDP service itself, or the VM's power/boot state. Don't keep re-checking NSG rules once this tool has confirmed they aren't the problem.
If the subnet has a custom route table attached — common in hub-and-spoke architectures routing traffic through a central firewall or network virtual appliance — RDP traffic that's correctly allowed by every NSG can still fail to reach the VM if a route sends it somewhere other than directly to the VM's network interface.
Custom route tables are the layer most often skipped in RDP troubleshooting, precisely because NSGs get blamed first and route tables are less visible in day-to-day operations. If IP Flow Verify confirms the NSG layer is allowing traffic but RDP still doesn't connect, a UDR silently routing the return traffic (or the inbound traffic itself, in an asymmetric routing scenario) through an unintended path is a strong next suspect, particularly in any environment using a hub-and-spoke or Virtual WAN topology with centralized firewall inspection.
Every layer covered so far operates outside the VM. Once network-layer traffic is confirmed to reach the VM's NIC correctly, the remaining possible failures live inside the guest operating system itself — and since RDP is the thing that's broken, you obviously can't use RDP to check it. Azure Run Command and Serial Console exist specifically for this situation.
It's a common assumption that once the NSG allows RDP traffic, the connection should work — but the NSG only controls traffic reaching the VM's network interface at the Azure platform level. The guest operating system's own Windows Firewall is a completely separate, independent control that has to also permit the traffic once it arrives. A Group Policy change, a security hardening script, or manual intervention inside the guest can disable or remove the built-in Remote Desktop firewall rule without touching the NSG at all — producing an identical "can't connect" symptom from a layer the NSG diagnosis already ruled out.
The final layer, and the one to check when every network and guest-OS-level check has come back clean: is the VM itself actually healthy, fully booted, and in a state capable of accepting connections at all?
- Boot diagnostics reviews the VM's console logs to determine whether the VM is reporting a startup issue — not enabled on every VM by default, so this step may require enabling it first, but when available it's often the fastest way to spot a VM stuck mid-boot or reporting an OS-level error.
- Serial console provides direct, text-based access to the VM's console independent of any network path — genuinely useful specifically because it doesn't depend on the network layer working at all, making it usable even when every other diagnostic approach in this guide has been blocked by the same underlying network issue.
A VM that's stopped (deallocated) or stuck in a failed provisioning state will fail RDP for reasons that have nothing to do with networking, firewalls, or the RDP service — and no amount of NSG or guest-firewall troubleshooting will fix a VM that simply isn't running. az vm show --resource-group myResourceGroup --name myVM --query "provisioningState, powerState" is a fast, first-principles check worth running before investing time in any of the network-layer diagnosis this guide covers — cheap to check, and it fully explains the failure if the answer is anything other than "VM running."
Everything in Layers 1 through 3 exists specifically to manage the risk of exposing RDP to the public internet at all. Azure Bastion removes that entire category of problem by design: it provides secure RDP and SSH access through the Azure portal or native client, with no public IP required on the target VM at all, no VPN client to manage, and no permanently-open internet-facing RDP port to defend.
| Aspect | Direct RDP (public IP + NSG) | Azure Bastion |
|---|---|---|
| Public IP on the VM | Required | Not required — VM stays fully private |
| Internet-facing open port | 3389 open to some source range | None — Bastion itself is the only internet-facing surface |
| Client requirement | Any RDP client | Browser (HTML5) or native client, via the Azure portal |
| NSG surface to defend | Direct VM-facing rule, ongoing maintenance | Bastion subnet rules — centrally managed, once |
| Recommended by Microsoft as the modern default | No — explicitly recommended against for permanent exposure | Yes, alongside Just-In-Time VM access |
Current Azure networking best-practice documentation states this directly: instead of opening RDP/SSH ports permanently, use Azure Bastion or just-in-time (JIT) VM access for secure management access. This isn't a preference — it's the explicit, current recommendation, and most of the troubleshooting complexity in Sections 2 through 5 of this guide (NSG rule layering, source IP scoping, route table interactions) exists specifically because RDP is exposed to the internet at all. Bastion doesn't just fix connection failures faster; it removes the ongoing operational burden of correctly maintaining that exposure in the first place.
Bastion's generic "Connection failed" error is, if anything, less informative than the direct-RDP client error — which makes knowing its specific, documented requirements even more valuable than trying to interpret the message itself.
| Bastion connection failure cause | Where to look |
|---|---|
| Missing rule on VM subnet NSG | The #1 documented cause — verify the rule above exists and is correctly scoped |
| Missing internal rules on AzureBastionSubnet NSG | Ports 8080/5701 (VirtualNetwork-to-VirtualNetwork) and 443 (AzureLoadBalancer) — omitting these blocks platform updates |
| VM stopped or deallocated | Check power state directly — Bastion cannot connect regardless of NSG correctness |
| VM in failed provisioning state | Check provisioning state — same effect as a stopped VM |
| Custom port requested on Basic SKU | Custom RDP/SSH ports require Standard or Premium SKU — Basic only supports the default ports |
Azure Bastion's hourly pricing begins the moment it's deployed, independent of outbound data usage or whether anyone is actively connecting through it. For non-production environments used intermittently, this is worth factoring into the cost model explicitly — deploying Bastion once per environment and leaving it running continuously accrues cost even during periods with zero actual RDP/SSH sessions.
Check VM power and provisioning state first
az vm show --resource-group myRG --name myVM --query "provisioningState, powerState" — cheapest possible check, and it fully explains the failure if the VM isn't actually running.
Confirm a network path exists — public IP and DNS, or private connectivity
Verify the VM has a public IP (if connecting over the internet) or that you have private connectivity (VPN, ExpressRoute, peering) to the VNet. No path means no amount of NSG tuning will help.
Run IP Flow Verify for the specific source IP and port
az network watcher test-ip-flow against inbound TCP 3389 from your actual connecting IP. A "Denied" result names the exact rule to fix; an "Allowed" result rules out NSGs entirely.
If AVNM is in use, run NSG Diagnostics to check security admin rules too
Confirms whether a centrally-managed security admin rule — evaluated before any NSG — is the actual source of the block, which IP Flow Verify alone may not fully distinguish.
If NSGs check out clean, verify routing with Next Hop
az network watcher show-next-hop confirms traffic destined for the VM is actually routed there, rather than intercepted by an unrelated UDR.
Use Run Command to check the guest OS firewall and RDP service
Confirm TermService is running and the built-in "Remote Desktop" firewall rule group is enabled — both independent of, and invisible to, every network-layer check performed so far.
If still unresolved, review boot diagnostics and serial console
Rules out a VM stuck mid-boot or reporting an OS-level startup error that no amount of network or firewall correctness would fix.
Test via Azure Bastion as a diagnostic shortcut, regardless of your eventual access method
If Bastion can connect where direct RDP couldn't, that specifically isolates the problem to the direct-RDP network path (public IP exposure, NSG scoping) rather than the VM or guest OS — a fast way to narrow the investigation even if Bastion isn't your long-term access strategy.
Validation & Verification: Confirm the Fix
Once a fix is applied at whichever layer was actually broken, confirm the specific mechanism, not just "RDP works now" — a connection succeeding once doesn't confirm the underlying issue is durably resolved.
Three conditions, together. One: the network-layer diagnostic (IP Flow Verify, and NSG Diagnostics if AVNM is in scope) explicitly confirms "Allowed" for the actual source IP and port in question, not just an assumption based on the rule you added. Two: the guest OS layer — TermService running, the Remote Desktop firewall rule group enabled — is confirmed directly via Run Command, since a network-layer fix alone doesn't rule out an independent guest-level block. Three: the fix is tested from the actual client and network path real users will use, not just from a convenient internal test VM — a fix validated only from inside the VNet can still fail for the internet-based or Bastion-based access pattern actual users depend on.
| Anti-pattern | Why it feels right | Why it isn't |
|---|---|---|
| Opening RDP to 0.0.0.0/0 "just to get it working" during troubleshooting | "Rule out NSG scoping quickly" | Creates real, immediate internet exposure — use IP Flow Verify with your actual source IP instead of widening the rule to test |
| Reconfiguring the NSG repeatedly without running IP Flow Verify first | "Let's just try a broader rule" | Guessing wastes time the diagnostic tool would have resolved in one call — always confirm the actual blocking rule before changing anything |
| Assuming a correct NSG means RDP will work | "The network layer is the whole story" | The guest OS firewall and RDP service are independent controls the NSG has no visibility into — a clean NSG diagnosis doesn't rule these out |
| Not checking VM power/provisioning state until every other layer is exhausted | "Obviously the VM is running" | It's the cheapest check in the entire guide — verify it first, not last, since it fully explains the failure when true |
| Treating Bastion's "Connection failed" as unrelated to standard NSG troubleshooting | "Bastion is a different service, different problem" | The overwhelming majority of Bastion failures are the same category of NSG misconfiguration this guide already covers — just on two NSGs instead of one |
| Keeping permanent public RDP exposure after diagnosing and fixing the immediate issue | "It's fixed now, no need to change the architecture" | Fixing the symptom without addressing the underlying exposure means the same class of problem — and the associated security risk — recurs. Migrate to Bastion or JIT once the immediate fire is out |
Key Takeaways
Frequently Asked Questions
Related FAVRITE Articles
- Enterprise Network Segmentation in Azure: Architecture Patterns That Actually Scale
- Hub-and-Spoke vs Azure Virtual WAN: Which Network Architecture Wins?
- Azure VM OS Disk Swap Guide
- Purging the Keys: Migrating Azure OpenAI Applications to Managed Identities and RBAC