Azure Troubleshooting

Cannot RDP or SSH into Your Azure VM? A Beginner's Connectivity Troubleshooting Checklist

You spun up your first Azure Virtual Machine ten minutes ago. The portal shows a reassuring green Running. You copy the public IP, open Remote Desktop (or a terminal), hit connect — and nothing. RDP spins for thirty seconds and dies with “can’t connect to the remote PC.” SSH sits silent, then prints Connection timed out or, worse, Connection refused. The VM is running, so why can’t you get in? This is, by a wide margin, the most common wall a beginner hits on Azure, and it is almost never the VM itself being broken. The VM is fine. Something on the path between your laptop and the VM’s login service is blocking you — and on Azure that path has five or six gates, any one of which closes the door.

The reason this feels so maddening is that “Running” only tells you the virtual hardware is powered on. It says nothing about whether a network security group (NSG) is allowing port 3389 (RDP) or 22 (SSH), whether the VM even has a public IP, whether the operating system’s own firewall is open, whether the login service inside the OS is listening, or whether the password/key you are typing is the one the VM expects. A connection has to pass all of those gates, in order, and the error you see (timeout vs refused vs an authentication prompt) is a clue to which gate slammed shut. Learn to read that clue and you stop randomly clicking and start diagnosing.

This is that checklist. We will treat “can’t connect” not as one bug but as a short, ordered list of layers — your network → Azure NSG → public IP/DNS → the OS firewall → the listener service → your credentials — and for each layer give you the exact thing to check, the precise az command or portal path that confirms it, and the fix. By the end you will work top-down: timeout? Suspect the network gates (NSG, IP, firewall). Refused or a login error? The VM is reachable and the problem is the service or your credentials. Knowing which half you are in takes the diagnosis from an hour of flailing to about five minutes.

What problem this solves

A blocked RDP/SSH connection is the single biggest source of “Azure is broken” support tickets from newcomers — and the overwhelming majority of them are self-inflicted configuration, not an Azure fault. Someone creates a VM, accepts a default that closes management ports (a good security default), and then cannot get in. Or they delete a public IP to save money and forget it was their only way in. Or they enable the Windows Firewall / Linux ufw inside the OS and lock out the very port they need. The VM keeps running and billing while they cannot touch it.

What breaks without this knowledge: you waste hours re-creating the VM (which often “fixes” it by accident, because the new VM gets the open default — teaching exactly the wrong lesson), you open a support ticket and wait, or you start opening ports far too wide (0.0.0.0/0 to the whole internet) just to make it work, which is how VMs get compromised within hours. The right move is almost never “rebuild it” — it is “walk the path, find the one closed gate, open just that gate for just your IP.”

Who hits this: every first-time VM user, and plenty of experienced ones after a change. It bites hardest right after creation (a closed-by-default NSG), after enabling an OS firewall, after deallocating and restarting (a dynamic public IP can change), after a network change (a route or NSG edit that removed your access), and after a forgotten password. The good news: every one of these is diagnosable from the portal or a single az command, and the fix is usually one line.

To frame the whole field before we dig in, here is every layer a connection must pass and the symptom you see when that specific layer is the one blocking you:

Layer (in order) What it controls Typical symptom when THIS layer blocks First place to check
Your local network Outbound 3389/22 from your office/ISP Timeout (often only from one network) Try a phone hotspot; Test-NetConnection
NSG (subnet + NIC) Inbound allow/deny on 3389/22 Connection timed out NSG inbound rules / effective rules
Public IP & DNS Whether the VM is reachable at all Timeout; or you’re using the wrong/old IP VM Overview → public IP
Azure Firewall / route (advanced) Forced tunneling, UDR, firewall in path Timeout despite NSG allow Effective routes; Network Watcher
OS firewall (Windows/Linux) In-guest allow on the port Refused or timeout after NSG is open Serial console; Run Command
Listener service (RDP/sshd) The login service running + on the right port Connection refused Run Command: check service/port
Credentials Username + password/SSH key Auth error (you connected!) Reset via VMAccess extension

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already have created at least one VM and know the basics: a VM has a network interface (NIC), the NIC lives in a subnet inside a virtual network (VNet), and the VM may or may not have a public IP attached. You should be able to run az in Azure Cloud Shell (the browser terminal in the portal, no install needed) and read its output. Knowing that RDP uses TCP port 3389 and SSH uses TCP port 22 is the one fact this entire article rotates around.

This sits at the very start of the Compute & Troubleshooting track. If you have not created a VM yet, do the Deploy Your First Azure VM: Portal and CLI, Step by Step walkthrough first — it sets up exactly the VM we will be unblocking here. The networking gate that blocks you most often is the NSG; the mental model for VNets, subnets and NSGs is covered in Azure Virtual Network: Subnets and NSGs. When the problem turns out to be deeper in the network (a route or a firewall in the path, not just an NSG), graduate to Diagnosing Azure VNet Connectivity: NSGs, UDRs, Effective Routes & Network Watcher. And the secure way to avoid exposing 3389/22 to the internet at all is Azure Bastion, the managed RDP/SSH broker we reach in the security section.

Core concepts

Four ideas make every diagnosis below obvious.

“Running” is not “reachable.” The VM’s power state (Running, Stopped, Stopped (deallocated)) only describes the virtual hardware. A running VM with no open port, no public IP, or a firewall blocking the listener is completely unreachable — and perfectly healthy. The first mental shift is to stop trusting the green dot and start walking the path. Every gate on that path must say “yes” before you get a login prompt.

The error you get tells you where on the path it failed. This is the most useful diagnostic fact in the whole article. A timeout means your connection packets left your machine and never got an answer — they were dropped silently, which is what an NSG deny, a missing public IP, or a route black-hole does. A connection refused means something answered “no” on that port — the packet reached the VM (so the NSG and IP are fine) but nothing was listening on that port, or the OS firewall actively rejected it. An authentication prompt or error means you connected to the service successfully — the entire network path works and the only thing left is your username/password/key. So: timeout → network layers; refused → OS firewall or listener; auth error → credentials. Memorise that and you have already halved every future incident.

Two NSGs can be in the path, and the effective rules are what count. An NSG is a list of allow/deny rules for inbound and outbound traffic. You can attach one to the subnet and another to the NIC, and both apply — a packet must be allowed by both to get through. Reading one NSG in isolation lies to you. Azure computes the combined result as effective security rules, and that combined view (portal or az network nic list-effective-nsg) is the only one you should trust when diagnosing. Inbound NSG rules are also evaluated by priority (lower number wins), so a broad deny at priority 200 will beat your allow at priority 300.

You can always get in through the back door — without RDP/SSH. This is the beginner’s lifeline. Even when the front door (RDP/SSH over the network) is completely shut, Azure gives you two paths that bypass the network entirely: the serial console (a direct console connection to the OS, like plugging a keyboard into the machine, which works even with every port closed) and Run Command (which runs a script inside the VM through the Azure control plane, no network login needed). With those, you can inspect and fix the OS firewall, restart the listener, or reset the password from outside — so a locked-out VM is rarely a rebuild.

The vocabulary in one table

Pin down every moving part before the deep sections. The glossary at the end repeats these for quick lookup:

Term One-line definition Why it matters to “can’t connect”
NSG (network security group) Allow/deny rules on inbound & outbound traffic Closed 3389/22 → timeout
Effective security rules The combined result of subnet + NIC NSGs The only NSG view you can trust
Public IP The internet-facing address on the VM’s NIC No public IP → unreachable from your laptop
Dynamic vs static IP Whether the public IP can change on stop/start A changed dynamic IP looks like “down”
RDP / port 3389 Remote Desktop to Windows The Windows front door
SSH / port 22 Secure shell to Linux The Linux front door
OS firewall In-guest firewall (Defender / ufw / firewalld) Open NSG but closed OS firewall → refused
Listener / sshd / TermService The service that answers on 22 / 3389 Not running → connection refused
Serial console Direct console to the OS, bypasses network Fix the OS even with all ports shut
Run Command Runs a script in the VM via Azure Inspect/repair without a login
Azure Bastion Managed RDP/SSH from the portal, no public IP The secure way in; avoids open 3389/22
VMAccess extension Resets passwords / SSH keys from Azure Recover credentials without rebuild

How to read the symptom: the three outcomes

Before any commands, classify what you actually see. Almost every “can’t connect” falls into one of three buckets, and the bucket tells you which sections below to read. Spend ten seconds here and save an hour later.

What you see What it means The path is… Read these sections
Spins, then timed out (RDP: “can’t connect”; SSH: Connection timed out) Packets left you and got no reply — silently dropped Blocked at a network gate (NSG, IP, route, OS firewall set to drop) NSG · Public IP · OS firewall
Fast connection refused (SSH: Connection refused; RDP: immediate failure) Something answered “no” on that port Network reaches the VM; service not listening or OS firewall rejecting OS firewall · Listener service
You get a login prompt but it fails (Permission denied, wrong password, CredSSP) You connected to the service Path is fully open; only credentials are wrong Credentials
DNS error (host not found) The name doesn’t resolve You’re using a bad/old DNS name or IP Public IP & DNS

Two quick reading notes that save the most time:

The path, gate by gate

Now walk the gates in order. Top-down: fix the network gates first (they cause timeouts), then the OS/service gates (refused), then credentials (auth errors). For each gate: what it is, how to confirm it’s the problem, and the fix with the exact command.

Gate 1 — Your own network (the free first check)

Before blaming Azure, rule out your side. Many corporate and some home/ISP networks block outbound 3389 and 22. If RDP/SSH times out from your office but you can’t test elsewhere, you may be staring at Azure for a problem that lives on your firewall.

Confirm. The fastest test is to try from a completely different network — tether to your phone’s hotspot and retry. If it works on the hotspot but not the office, your network is the blocker. From Windows you can probe the port reachability directly:

# Does TCP 3389 to the VM's public IP even open from here?
Test-NetConnection -ComputerName 20.40.50.60 -Port 3389
# TcpTestSucceeded : True  -> the path is open to here; problem is auth/service
# TcpTestSucceeded : False -> blocked somewhere (your net, NSG, IP, or OS firewall)

On macOS/Linux, nc -vz 20.40.50.60 22 does the same for SSH. Fix. If it’s your corporate firewall, ask IT to allow outbound to the VM, or use Azure Bastion (which connects over HTTPS/443, almost always open) instead of raw 3389/22. This single check is free and rules out an entire class of false alarms.

Gate 2 — The NSG (the number-one cause of timeouts)

The network security group is the gate that blocks first-time users most often, because secure-by-default templates do not open 3389/22 to the world. If your inbound NSG rules don’t allow your port from your source IP, your connection packets are dropped and you get a timeout — never a refusal, because nothing ever answers.

Confirm. Two ways. In the portal: VM → Networking → Inbound port rules shows the rules, but the truthful view (combining subnet + NIC NSGs) is VM → Networking → check the effective rules or, more directly, the NIC’s Effective security rules. Via CLI, pull the effective NSG so you see the combined result, not one NSG in isolation:

# The combined inbound rules actually applied to this VM's NIC
az network nic list-effective-nsg \
  --name vm-web01VMNic --resource-group rg-lab \
  --query "value[].effectiveSecurityRules[?direction=='Inbound' && (destinationPortRange=='3389' || destinationPortRange=='22' || destinationPortRange=='*')].{name:name, access:access, prio:priority, srcPrefix:sourceAddressPrefix, port:destinationPortRange}" \
  -o table

Look for an Allow on your port whose source matches your IP and whose priority is lower (higher precedence) than any matching Deny. If you only see the default DenyAllInBound (priority 65500), there is no allow rule and that’s your problem.

Fix. Add a narrow inbound allow rule — for your IP only, never 0.0.0.0/0. First find your public IP (search “what is my IP” or curl ifconfig.me), then:

# Allow RDP (3389) from YOUR IP only. Use 22 for SSH on Linux VMs.
az network nsg rule create \
  --resource-group rg-lab --nsg-name vm-web01-nsg \
  --name Allow-RDP-MyIP --priority 300 \
  --direction Inbound --access Allow --protocol Tcp \
  --source-address-prefixes 203.0.113.25/32 \
  --destination-port-ranges 3389
// The same rule in Bicep — note the /32 to lock it to a single source IP
resource rdpRule 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
  parent: nsg
  name: 'Allow-RDP-MyIP'
  properties: {
    priority: 300
    direction: 'Inbound'
    access: 'Allow'
    protocol: 'Tcp'
    sourceAddressPrefix: '203.0.113.25/32'   // your IP, NOT '*' or '0.0.0.0/0'
    sourcePortRange: '*'
    destinationAddressPrefix: '*'
    destinationPortRange: '3389'              // '22' for SSH
  }
}

The NSG rule fields you must get right, and the trap in each:

Field What it means Common mistake Correct value
direction Inbound vs outbound Editing an outbound rule Inbound for RDP/SSH
access Allow or Deny Leaving a higher-priority Deny in front Allow
priority 100–4096; lower wins Allow at 400 behind a Deny at 200 Put the Allow below the Deny number
protocol Tcp / Udp / * Choosing Udp Tcp (both 3389 and 22 are TCP)
sourceAddressPrefix Who may connect * / 0.0.0.0/0 (whole internet) Your IP /32
destinationPortRange The port opened Typo (3399, 222) 3389 (RDP) or 22 (SSH)

A note that catches people: priority. NSG inbound rules are evaluated lowest-number-first, and the first match wins. If a template added a Deny at priority 200 and you add an Allow at 300, the Deny still wins. Either lower your Allow’s number or remove the broad Deny.

Gate 3 — The public IP and DNS

If the VM has no public IP, you simply cannot reach it from your laptop over the internet — there is no address to connect to. And if it has a dynamic public IP, that address can change when the VM is stopped (deallocated) and started again, so the IP you saved yesterday may now belong to someone else’s VM, giving you a timeout against a stranger.

Confirm. Check whether a public IP exists and what it is right now:

# Show the VM's current public IP (empty result = no public IP attached)
az vm list-ip-addresses --name vm-web01 --resource-group rg-lab \
  --query "[].virtualMachine.network.publicIpAddresses[].{ip:ipAddress, alloc:?, fqdn:dnsSettings.fqdn}" -o table

In the portal, VM → Overview shows the public IP (or a blank where it would be). The allocation method (Static vs Dynamic) is on the public IP resource → Configuration. Fix. If there’s no public IP and you intended one, attach a static public IP; if it’s dynamic and keeps changing, convert it to Static so it stays put:

# Make the public IP static so it never changes on stop/start
az network public-ip update --name vm-web01-ip --resource-group rg-lab \
  --allocation-method Static

The cleaner answer for production is to not expose a public IP at all and connect through Azure Bastion (next gate, and the security section). The public-IP states and what each means for connecting:

Situation What you see Why you can’t connect Fix
No public IP Blank IP on Overview No internet-facing address exists Attach a public IP, or use Bastion
Dynamic IP changed Old IP times out The address moved on deallocate/start Convert to Static, use the new IP
Using the DNS name, stale Host-not-found The FQDN points at an old/released IP Re-check the current IP/FQDN
Private IP only by design 10.x / 172.16-31.x / 192.168.x It’s not internet-reachable on purpose Connect via Bastion / VPN / jumpbox

Gate 4 — The OS firewall (open NSG, still no entry)

Here is the gate that fools people who did open the NSG correctly. Inside the guest OS there is a second firewall — Windows Defender Firewall on Windows, ufw or firewalld on Linux — and it has its own rules. If the NSG allows 3389 but Windows Firewall blocks it (or you enabled ufw on Linux without allowing 22), the packet reaches the VM and is then dropped or rejected inside the OS. Depending on the firewall mode this shows as a timeout (drop) or a connection refused (reject).

Confirm. You usually can’t RDP/SSH in to check the firewall — that’s the whole problem — so use Run Command, which executes a script inside the VM through Azure with no network login:

# Windows: is the RDP firewall rule enabled and the port listening?
az vm run-command invoke --command-id RunPowerShellScript \
  --name vm-web01 --resource-group rg-lab \
  --scripts "Get-NetFirewallRule -DisplayGroup 'Remote Desktop' | Select DisplayName,Enabled; (Get-NetTCPConnection -LocalPort 3389 -State Listen -EA SilentlyContinue) | Select LocalAddress,LocalPort"
# Linux: is ufw blocking 22, and is sshd listening?
az vm run-command invoke --command-id RunShellScript \
  --name vm-lin01 --resource-group rg-lab \
  --scripts "ufw status verbose 2>/dev/null; ss -tlnp | grep ':22 ' || echo 'sshd NOT listening on 22'"

Fix. Re-enable the firewall rule (don’t disable the firewall — open the one port). Via Run Command:

# Windows: re-enable the built-in Remote Desktop firewall rules
az vm run-command invoke --command-id RunPowerShellScript \
  --name vm-web01 --resource-group rg-lab \
  --scripts "Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'; Set-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0"
# Linux: allow SSH through ufw (keeps the firewall on, opens only 22)
az vm run-command invoke --command-id RunShellScript \
  --name vm-lin01 --resource-group rg-lab \
  --scripts "ufw allow 22/tcp && ufw reload && ufw status"

The OS-firewall situations and how the symptom differs:

OS Tool Symptom when it blocks Confirm with Fix
Windows Defender Firewall Timeout or refused on 3389 Get-NetFirewallRule -DisplayGroup 'Remote Desktop' Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'
Windows fDenyTSConnections reg key Refused on 3389 Read the registry key (above) Set the key to 0
Linux ufw Timeout (default deny) on 22 ufw status verbose ufw allow 22/tcp
Linux firewalld Timeout/refused on 22 firewall-cmd --list-all firewall-cmd --add-service=ssh --permanent && firewall-cmd --reload

Gate 5 — The listener service (the cause of “connection refused”)

If you get a fast connection refused rather than a timeout, the packet is reaching the VM but nothing is listening on that port. The login service itself is down: on Windows that’s the Remote Desktop Services (TermService); on Linux it’s the SSH daemon (sshd). A crashed service, a service set to “Disabled,” or sshd configured to listen on a non-default port all produce refusals.

Confirm. Check the service and the listening port via Run Command:

# Windows: is the RDP service running?
az vm run-command invoke --command-id RunPowerShellScript \
  --name vm-web01 --resource-group rg-lab \
  --scripts "Get-Service TermService | Select Name,Status,StartType"
# Linux: is sshd active, and on which port?
az vm run-command invoke --command-id RunShellScript \
  --name vm-lin01 --resource-group rg-lab \
  --scripts "systemctl is-active sshd || systemctl is-active ssh; grep -E '^Port' /etc/ssh/sshd_config || echo 'Port 22 (default)'"

Fix. Start (and re-enable) the service:

# Windows: start RDP and set it to start automatically
az vm run-command invoke --command-id RunPowerShellScript \
  --name vm-web01 --resource-group rg-lab \
  --scripts "Set-Service TermService -StartupType Automatic; Start-Service TermService"
# Linux: restart sshd and ensure it starts on boot
az vm run-command invoke --command-id RunShellScript \
  --name vm-lin01 --resource-group rg-lab \
  --scripts "systemctl enable --now ssh 2>/dev/null || systemctl enable --now sshd; systemctl status ssh --no-pager 2>/dev/null | head -5"

If sshd was deliberately moved to a non-standard port, either connect to that port (ssh -p 2222 ...) and open it in the NSG, or move it back to 22. When even Run Command can’t fix it (e.g. the OS won’t boot far enough), the serial console is your last resort — see the diagnostics section.

Gate 6 — Credentials (you connected, but it won’t let you in)

If you reached a password prompt or got Permission denied (publickey) / a CredSSP error, the network is perfect — this is purely authentication. The most common causes: you forgot the local admin password (Windows), you’re using the wrong username, the SSH public key on the VM doesn’t match your private key, or your client is sending the wrong key.

Confirm. First, double-check the username you set at creation — it is not Administrator or root by default; it’s whatever you typed (e.g. azureuser). Then, for SSH, run the client in verbose mode to see which key it offered and why it was rejected:

# -v shows the auth handshake: which key was offered, and 'Permission denied (publickey)'
ssh -v azureuser@20.40.50.60

Fix. You do not need to rebuild the VM. Azure’s VMAccess extension resets the password (Windows) or repairs the SSH public key (Linux) from the control plane:

# Windows: reset the local admin password (creates the user if missing)
az vm user update --name vm-web01 --resource-group rg-lab \
  --username azureuser --password 'N3wStr0ng!Pass2026'
# Linux: push a fresh SSH public key for the user (repairs key mismatch)
az vm user update --name vm-lin01 --resource-group rg-lab \
  --username azureuser --ssh-key-value "$(cat ~/.ssh/id_rsa.pub)"

The credential failure modes and how to tell them apart:

Symptom Likely cause Confirm Fix
Wrong password (Windows) Forgotten/incorrect admin password You see the password prompt az vm user update to reset it
Permission denied (publickey) VM’s public key ≠ your private key ssh -v shows key offered then denied az vm user update --ssh-key-value
Too many authentication failures Client offering many wrong keys ssh -v lists several keys tried ssh -i ~/.ssh/right_key -o IdentitiesOnly=yes ...
CredSSP / “authentication error” (RDP) Client/host NLA patch mismatch RDP error mentions CredSSP/Oracle Patch both ends; or temporarily relax NLA
Wrong username Using root/Administrator You guessed the name Use the admin name set at creation

Architecture at a glance

Picture the connection as a relay race where the baton (your TCP packet on port 3389 or 22) must be passed cleanly through six runners, in order, before you see a login screen. It leaves your laptop and crosses your local/ISP network (runner one — a corporate firewall can drop it here). It arrives at Azure and hits the VM’s public IP (runner two — if there’s no public IP, the baton has nowhere to go). It then meets the NSG, the combined subnet-plus-NIC rule set (runner three — the gate that most often drops first-timers, producing a timeout). Past the NSG, if any route or Azure Firewall sits in the path it must permit and forward the traffic (runner four — usually transparent, but a forced-tunnel route can black-hole it). The baton reaches the VM’s NIC and enters the operating system, where the in-guest firewall — Windows Defender Firewall or Linux ufw/firewalld — gets one more say (runner five — an NSG can be wide open and this still drops you). Finally it reaches the listener service, TermService for RDP or sshd for SSH (runner six — if that service isn’t running you get an instant connection refused). Only after all six hand off cleanly do your credentials get checked.

The whole diagnostic method falls out of this picture. A timeout means a runner dropped the baton silently — and that’s always a network-ish gate: the ISP, a missing IP, the NSG, a route, or an OS firewall in drop mode. A connection refused means the baton reached the finish but the last runner said “no one’s here” — the service isn’t listening, or the OS firewall actively rejected it. An auth error means the baton finished the race and only the final credential check failed. So the first question is never “is the VM broken?” — it’s “which runner dropped it?” — and the symptom (timeout vs refused vs auth) tells you whether to look at the first three gates, the OS gates, or just your password. Crucially, two of Azure’s tools — the serial console and Run Command — let you teleport past every runner straight into the OS, which is how you fix the inner gates when the front door is shut.

Real-world scenario

Nutio Labs is a four-person analytics startup in Bengaluru. A junior engineer, on day three, spins up a Windows Server 2022 VM (a B2s) in Central India to host a small internal reporting tool, using the portal’s recommended secure defaults — which, correctly, do not open RDP to the internet. He sets the admin user to nutioadmin, notes the public IP, and tries to RDP in from the office. It spins for thirty seconds and dies: “Remote Desktop can’t connect to the remote computer.” The VM shows Running. He restarts it. Same result. He deletes it and rebuilds it twice, burning an afternoon and a few hundred rupees of his trial credit, and is about to file a “Azure RDP is broken” ticket.

The lead architect walks over and asks one question: “Timeout, or refused?” It’s a timeout — so the problem is a network gate, not the VM. They open Cloud Shell and pull the effective NSG rules with az network nic list-effective-nsg. The output is damning in its simplicity: the only inbound rule matching 3389 is the default DenyAllInBound at priority 65500. There is no allow rule at all — the secure template never created one, by design. The VM was never reachable; restarting and rebuilding never could have helped, because every rebuild reproduced the same closed default.

The fix takes one command. They grab the office’s public IP (curl ifconfig.me203.0.113.25) and add a single narrow rule: az network nsg rule create ... --name Allow-RDP-Office --priority 300 --source-address-prefixes 203.0.113.25/32 --destination-port-ranges 3389. Thirty seconds later, RDP connects to a login prompt. The credentials (nutioadmin and the password set at creation) work first try — confirming the network really had been the entire problem.

But the architect doesn’t stop there, because an internal tool with RDP open to even one office IP is a habit she wants to break early. Over the next hour they do it right: they remove the public IP entirely, deploy Azure Bastion into the VNet, and from then on the team connects via the portal over HTTPS — no public RDP port anywhere, nothing for an internet scanner to find. For the rare case Bastion is overkill, she shows him just-in-time (JIT) VM access, which keeps 3389 closed and opens it for your IP for a fixed window only when you request it. The lesson she writes on the team wiki: “‘Running’ means powered on, not reachable. A timeout is a network gate — check the effective NSG before you ever rebuild. And don’t put 3389 on the internet; that’s what Bastion is for.” The afternoon’s painful rebuild loop became a five-minute diagnosis and a permanently safer pattern.

The incident as a timeline, because the order of moves is the lesson:

Time Symptom Action taken Effect What it should have been
Day 3, 14:00 RDP timeout, VM “Running” Restart the VM No change Ask: timeout or refused?
14:15 Still timeout Delete + rebuild VM (×2) No change, credit burned Don’t rebuild a network problem
15:30 About to file ticket Architect: “timeout = network gate” Reframed correctly
15:35 Root cause az network nic list-effective-nsg Only DenyAllInBound matched 3389 This is the breakthrough
15:37 Fixed Add Allow-RDP from office /32 Login prompt appears The actual one-line fix
16:30 Hardened Remove public IP, deploy Bastion + JIT No public 3389 anywhere The right end state

Advantages and disadvantages

Azure’s “secure by default, but diagnosable” posture both causes this beginner pain and makes it quick to solve. Weigh it honestly:

Advantages (why Azure helps you here) Disadvantages (why it bites a beginner)
Ports are closed by default, so a fresh VM isn’t instantly attacked The closed default is the #1 “can’t connect” surprise — you must add a rule
Serial console and Run Command let you fix the OS even with every port shut They’re unfamiliar; beginners don’t know the back door exists and rebuild instead
Connection troubleshoot (Network Watcher) pinpoints the exact blocking hop It’s buried in a separate Network Watcher blade, not the VM’s error message
VMAccess resets passwords/keys without a rebuild Easy to miss; people recreate the VM to “fix” a forgotten password
Effective security rules show the true combined NSG result Reading one NSG in isolation misleads — two NSGs can both apply
Bastion + JIT remove the need to expose 3389/22 at all Extra setup/cost; tempting to just open 0.0.0.0/0 instead
The error type (timeout/refused/auth) is a precise diagnostic clue Only if you know to read it — otherwise every failure looks identical

The model is right: closing management ports by default has saved countless VMs from being compromised within minutes of creation (internet scanners hit 3389/22 constantly). The cost is a learning curve — but it’s a small one, and every gate above is one command to inspect and one to fix. The disadvantages are all “you didn’t know the tool existed,” which is exactly what this article fixes.

Hands-on lab

Reproduce the classic “timeout because the NSG is closed,” diagnose it the right way, then fix it — all on a free-tier-friendly B1s Linux VM (delete at the end so it costs nothing). Run in Cloud Shell (Bash).

Step 1 — Variables and resource group.

RG=rg-connect-lab
LOC=centralindia
VM=vm-connect-lab
az group create -n $RG -l $LOC -o table

Step 2 — Create a small Linux VM, generating an SSH key.

az vm create -n $VM -g $RG \
  --image Ubuntu2204 --size Standard_B1s \
  --admin-username azureuser --generate-ssh-keys \
  --public-ip-sku Standard -o table
# Note the publicIpAddress in the output.

Step 3 — Reproduce the bug: remove the SSH allow rule. A default az vm create opens 22, so we’ll close it to simulate the secure-template situation:

# Find the auto-created NSG and delete its SSH rule to break access
NSG=$(az network nsg list -g $RG --query "[0].name" -o tsv)
az network nsg rule delete -g $RG --nsg-name $NSG --name default-allow-ssh 2>/dev/null \
  || az network nsg rule list -g $RG --nsg-name $NSG -o table   # list to find the SSH rule name

Step 4 — Watch it fail. Try to SSH (replace with your IP):

ssh azureuser@<publicIpAddress>
# Hangs ~30s then: "Connection timed out"  <- a TIMEOUT = a network gate

A timeout, exactly as predicted — a network gate is dropping you.

Step 5 — Diagnose the right way: read the effective NSG.

NIC=$(az vm show -n $VM -g $RG --query "networkProfile.networkInterfaces[0].id" -o tsv)
az network nic list-effective-nsg --ids $NIC \
  --query "value[].effectiveSecurityRules[?direction=='Inbound' && access=='Allow' && (destinationPortRange=='22' || destinationPortRange=='*')].{name:name,port:destinationPortRange,src:sourceAddressPrefix}" \
  -o table
# Likely shows no Allow for 22 -> the SSH gate is closed. Confirmed root cause.

Step 6 — Fix it for your IP only.

MYIP=$(curl -s ifconfig.me)
az network nsg rule create -g $RG --nsg-name $NSG \
  --name Allow-SSH-MyIP --priority 300 \
  --direction Inbound --access Allow --protocol Tcp \
  --source-address-prefixes ${MYIP}/32 --destination-port-ranges 22 -o table

Step 7 — Connect.

ssh azureuser@<publicIpAddress>
# You're in. The network gate was the whole problem.

Step 8 — Bonus: use Run Command to peek inside without SSH. Proves the back door works:

az vm run-command invoke --command-id RunShellScript -n $VM -g $RG \
  --scripts "ss -tlnp | grep ':22 ' && echo 'sshd is listening'"

Step 9 — Teardown (so it costs nothing).

az group delete -n $RG --yes --no-wait

You just reproduced, diagnosed and fixed the single most common Azure connectivity problem — and proved you can inspect a VM with no working SSH at all.

Common mistakes & troubleshooting

This is the centerpiece — the playbook to keep open at the moment you can’t connect. Find your symptom, confirm the cause with the exact command/portal path, apply the fix. It spans the everyday beginner blockers and a few deeper ones. Work it top-down: the rows are roughly ordered by how often they’re the culprit.

# Symptom Root cause Confirm (exact command / portal path) Fix
1 RDP/SSH times out, VM is “Running” NSG has no allow rule for your port/IP az network nic list-effective-nsg → only DenyAllInBound matches Add inbound Allow for your IP /32 on 3389/22
2 Timed out; only the office network fails Your corporate/ISP firewall blocks outbound 3389/22 Works on a phone hotspot; Test-NetConnection -Port 3389 fails on office only Ask IT to allow outbound, or use Bastion (443)
3 Timed out; IP “worked yesterday” Dynamic public IP changed on stop/start az vm list-ip-addresses shows a different IP than you saved Use the new IP; convert to Static
4 Timed out; no IP to connect to VM has no public IP VM → Overview shows blank public IP Attach a public IP, or connect via Bastion
5 NSG is open but still times out OS firewall (Defender / ufw) dropping the port Run Command: Get-NetFirewallRule -DisplayGroup 'Remote Desktop' / ufw status Enable the RDP rule / ufw allow 22/tcp
6 Fast connection refused Listener service (TermService / sshd) not running Run Command: Get-Service TermService / systemctl is-active ssh Start + enable the service
7 Refused; service is running sshd moved to a non-standard port (not 22) Run Command: grep ^Port /etc/ssh/sshd_config Connect with -p <port> and open it in NSG, or revert to 22
8 Connected, but wrong password (Windows) Forgotten/incorrect admin password You reach the password prompt az vm user update --username <u> --password ...
9 Permission denied (publickey) (Linux) VM’s public key ≠ your private key ssh -v shows key offered, then denied az vm user update --ssh-key-value "$(cat ~/.ssh/id_rsa.pub)"
10 Too many authentication failures Client offers many wrong keys before the right one ssh -v lists several keys tried ssh -i <key> -o IdentitiesOnly=yes user@ip
11 RDP “authentication error” / CredSSP NLA/CredSSP patch mismatch between client and host RDP error text mentions CredSSP/Oracle Remediation Patch both ends; temporarily relax NLA via Run Command
12 Wrong username rejected Using root/Administrator instead of the admin you set You guessed the name Use the admin username from VM creation
13 Timed out even with NSG + OS firewall open A route/UDR or Azure Firewall black-holes the traffic Network Watcher → Connection troubleshoot; effective routes Fix the route; allow the flow on the firewall
14 Was working, suddenly times out VM is Stopped (deallocated), or a recent NSG/route change VM → Overview power state; Activity log for recent NSG edits Start the VM; revert the offending change
15 RDP connects then drops instantly RDP licensing / too many sessions, or NLA loop Event log via serial console; quser via Run Command Free a session; check RD licensing; relax NLA

When even Run Command isn’t enough, here are the error strings you’ll actually see and what each one points to:

Error string you see On What it really means First confirm Fix
Connection timed out SSH Packets dropped somewhere in the path Effective NSG / OS firewall Open NSG for your IP; check ufw
Connection refused SSH Reached the VM; nothing listening on the port systemctl is-active ssh; port in sshd_config Start sshd; fix the port
Permission denied (publickey) SSH Network fine; key mismatch ssh -v; the VM’s authorized_keys az vm user update --ssh-key-value ...
Host key verification failed SSH Your known_hosts has a stale key for that IP The IP was reused/rebuilt ssh-keygen -R <ip> then reconnect
“Remote Desktop can’t connect to the remote computer” RDP Generic — usually a timeout (network gate) Effective NSG; public IP Open NSG; verify IP
“An authentication error has occurred (CredSSP)” RDP NLA/CredSSP patch mismatch RDP error detail Patch both sides; relax NLA temporarily
“The logon attempt failed” RDP You connected; wrong credentials The login prompt appeared Reset password via az vm user update

Per-symptom detail on the ones that fool everyone

The NSG you edited isn’t the NSG in the path. A VM can have an NSG on its NIC and another on its subnet, and people edit one while the other does the denying. Never reason from a single NSG — pull az network nic list-effective-nsg (or the portal’s effective security rules), which merges both and shows the rule that actually wins by priority. If your new Allow at priority 300 isn’t working, look for a Deny at a lower number in front of it.

OS firewall after the NSG is “open.” The maddening one: you fixed the NSG, the timeout persists, and you conclude Azure is broken. It isn’t — the in-guest firewall is dropping the packet after it passed the NSG. Because you can’t log in to check, use Run Command to inspect Windows Defender Firewall or ufw, then enable the single port. Don’t disable the firewall wholesale; open the one rule.

Forgotten password is not a rebuild. Beginners delete the VM to escape a forgotten Windows password. Never do that — az vm user update --username <name> --password <new> resets it through the VMAccess extension in under a minute, disk and data intact. Same on Linux with --ssh-key-value to push a fresh public key when your key stopped matching.

Best practices

Security notes

The reason RDP/SSH is closed by default is that exposing it is genuinely dangerous, so fix connectivity in a way that stays secure rather than punching a hole and forgetting it.

Cost & sizing

Diagnosing connectivity is essentially free — the tools (NSG rules, effective rules, Run Command, serial console, boot diagnostics, Network Watcher Connection troubleshoot) cost nothing or a few paise. The real cost questions are about the VM you’re trying to reach and the secure-access pattern you choose.

Item What drives the cost Rough figure (India/USD) Free-tier / note
The VM (e.g. B1s) Size + region + hours running B1s ≈ ₹700–900 / ~US$8–11 per month Stopped (deallocated) VMs incur no compute charge
Static public IP Standard SKU IP, billed hourly ≈ ₹300–400 / ~US$3.6 per month Dynamic Basic IPs are cheaper but can change
Azure Bastion Hours + outbound data; Basic vs Standard SKU Basic ≈ ₹12–14 / ~US$0.19 per hour (~₹9,000+/mo if left on) No per-VM agent; one Bastion serves the whole VNet
NSG / Run Command / serial console Effectively free Use freely while diagnosing
Network Watcher Connection troubleshoot checks A few paise per check First diagnostic to reach for

Sizing guidance for the access pattern, not the VM: for a single lab VM you poke at occasionally, a static public IP + JIT + a narrow NSG rule is the cheapest secure option (you pay for the IP and the VM; JIT and the NSG are free). For a fleet of VMs, one Azure Bastion for the VNet is usually cheaper and safer than putting a public IP on every VM — Bastion’s hourly cost is fixed regardless of VM count, and you delete a whole category of risk. The biggest hidden cost is a VM left Running that you locked yourself out of: it bills 24/7 while you can’t use it — which is exactly why fixing access (not rebuilding) matters. And remember a VM in Stopped (deallocated) state stops compute charges entirely, so if you’re done for the day, deallocate it (az vm deallocate), accepting that a dynamic IP may change on the next start.

Interview & exam questions

Q1. A VM shows “Running” but RDP times out. What’s your first diagnostic step and why? Pull the effective NSG rules for the VM’s NIC (az network nic list-effective-nsg or the portal’s effective security rules) and check for an inbound Allow on 3389 from your source IP. A timeout means packets are being silently dropped, and the NSG is the most common dropper; the effective view is authoritative because subnet and NIC NSGs both apply.

Q2. SSH returns Connection refused rather than Connection timed out. What does that distinction tell you? Refused means the packet reached the VM and something answered “no” on that port — so the NSG, public IP and route are fine. The problem is inside the OS: sshd isn’t running (or is on a different port), or the OS firewall is actively rejecting. Timeout, by contrast, points at a network gate dropping the packet before it’s answered.

Q3. You opened the NSG for 3389 but RDP still times out. What’s the likely cause? The in-guest OS firewall (Windows Defender Firewall) is dropping the port after it passes the NSG. Confirm and fix with Run Command (Get-NetFirewallRule -DisplayGroup 'Remote Desktop', then Enable-NetFirewallRule) since you can’t RDP in to check. An NSG allow and an OS-firewall allow are two separate gates.

Q4. How do you inspect or repair a VM when you have no working RDP/SSH at all? Use Run Command (runs a script inside the VM via the Azure control plane) for most checks/fixes, and the serial console (a direct console to the OS that bypasses all networking) when the OS won’t boot far enough for Run Command. Neither needs an open port or working credentials path.

Q5. A user forgot the Windows admin password. Do you rebuild the VM? No. Use az vm user update --username <name> --password <new>, which drives the VMAccess extension to reset the local admin password in place, preserving the OS disk and data. On Linux, the same command with --ssh-key-value repairs a mismatched SSH public key.

Q6. What’s the difference between a dynamic and a static public IP for connectivity? A dynamic public IP can change when the VM is stopped (deallocated) and restarted, so a saved IP may stop working and look like an outage. A static IP is reserved and never changes. For anything you connect to regularly, use static — or remove the public IP and use Bastion.

Q7. Why shouldn’t you open 3389/22 to 0.0.0.0/0, even temporarily? Internet scanners continuously probe 3389 and 22; a VM with those ports open to the whole internet is typically discovered and brute-forced within minutes. Scope the rule to your IP /32, and prefer JIT access (port closed until you request a time-boxed opening) or Bastion (no public port at all).

Q8. Two NSGs are attached — one to the subnet, one to the NIC. How is the result computed? Both NSGs apply, and a packet must be allowed by both to pass; within each NSG, rules are evaluated by priority (lowest number first, first match wins). The effective security rules view merges subnet and NIC NSGs into the single set that actually governs the traffic — always diagnose from that, not one NSG.

Q9. RDP fails with a CredSSP / “authentication error.” Is this a network problem? No — reaching a CredSSP error means you connected to the RDP service, so the network path is open. It’s an NLA/CredSSP patch-level mismatch between client and host. Patch both ends; if you must connect immediately, temporarily relax NLA via Run Command, then re-enable it.

Q10. Which Azure tool tells you the exact hop where a connection is blocked, and where does it live? Connection troubleshoot in Network Watcher tests connectivity from a source (e.g. a VM) to a destination and reports the blocking hop — NSG rule, route, or unreachable target. It lives under the Network Watcher resource, not the VM blade, which is why beginners miss it.

These map to the AZ-104 (Azure Administrator) objectives on configuring VM networking, NSGs and connectivity, and touch AZ-700 (Network Engineer) for the deeper routing/Network Watcher items.

Quick check

  1. RDP/SSH times out on a “Running” VM. Which half of the path do you investigate first — and name the most likely single cause.
  2. You get Connection refused on SSH. What does that prove about the network, and where do you look next?
  3. You opened the NSG for 22 but SSH still times out. What’s the next gate to check, and which tool inspects it without logging in?
  4. The public IP that worked yesterday now times out. What’s the likely reason, and the permanent fix?
  5. A teammate forgot the Windows admin password. What’s the one-command fix, and what must you not do?

Answers

  1. The network gates (timeout = silently dropped packets). The most likely single cause is an NSG with no inbound Allow for your port/IP — confirm with az network nic list-effective-nsg and add a narrow Allow for your IP /32.
  2. It proves the packet reached the VM (NSG, public IP and route are all fine). Look inside the OS: the listener (sshd) isn’t running or is on a non-standard port, or the OS firewall is rejecting — check via Run Command.
  3. The OS firewall (Windows Defender Firewall / ufw). Inspect it with Run Command (Get-NetFirewallRule -DisplayGroup 'Remote Desktop' or ufw status) since you can’t log in, then enable the single port.
  4. The public IP is dynamic and changed on a stop/start. Use the current IP from az vm list-ip-addresses, and convert it to Static so it never moves again (or drop the public IP and use Bastion).
  5. az vm user update --username <name> --password <new> (it resets via the VMAccess extension, data intact). Do not delete and rebuild the VM.

Glossary

Next steps

AzureVirtual MachinesTroubleshootingRDPSSHNSGNetworkingBastion
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading