3389
The Remote Desktop listener on Windows — the port that is already taken when the service will not start, and silently dropped when the client just spins.
Run the line for your platform. Each one names the process holding the port and the address it bound to, which is usually the whole answer.
netstat -ano | findstr ":3389"This is the sequence Microsoft's own RDP troubleshooting guide uses. `-a` displays all connections and listening ports, `-n` keeps addresses and ports numeric, and `-o` adds the owning process ID, which is the last column. Look for a row whose Local Address ends in `:3389` with a State of LISTENING — `0.0.0.0:3389` is every interface, `127.0.0.1:3389` would be loopback only and could never accept a remote session. Then resolve the PID with `tasklist /svc | findstr "1234"`, not plain `tasklist`: the listener lives inside a `svchost.exe`, and only the `/svc` column reveals that the service behind it is `TermService`. If the PID belongs to anything else, that program took the port first — move it rather than moving RDP.
qwinstaRun this before hunting for a thief, because it answers a different question: is there a listener at all? A row named `rdp-tcp` with a State of `Listen` means the RDP listener is working and the problem is further out — firewall, routing, or credentials. No such row means the listener never came up, and `netstat` will show nothing on 3389 for a reason that has nothing to do with another process. In that case check that the Remote Desktop Services (`TermService`) and Remote Desktop Services UserMode Port Redirector (`UmRdpService`) services are running, and that `fDenyTSConnections` is `0`.
sudo ss -tlnp 'sport = :3389'A Linux box listening on 3389 is running xrdp or a similar server, not anything from Microsoft. `-t` displays TCP sockets, `-l` only listening ones, `-n` skips service-name resolution and `-p` shows the owning process; `sport = :3389` filters on the source port. The Local Address:Port column is the bind — `0.0.0.0:3389` reaches the network, `127.0.0.1:3389` is loopback only and is the usual configuration when xrdp is meant to be reached through an SSH tunnel. The owner prints as `users:(("xrdp",pid=987,fd=11))`. Where `ss` is unavailable, `sudo netstat -tlnp | grep :3389` gives the same answer with a slash-separated pid/program column; both need superuser privileges to name processes you do not own.
sudo lsof -nP -iTCP:3389 -sTCP:LISTENmacOS ships an RDP client, not an RDP server, so anything listening here is a container, a VM's forwarded port, or a third-party server you installed. `-n` and `-P` keep addresses and ports numeric, `-iTCP:3389` selects the port and `-sTCP:LISTEN` narrows it to listeners. COMMAND and PID name the owner, and NAME is the bind address: `127.0.0.1:3389` is loopback only, `*:3389` is every interface. A COMMAND of `com.docker.backend` means a published container port, so stop the container rather than the process. No output — lsof exits 1 — means nothing is listening, and the port is genuinely free.
`nmap -Pn -p 3389 host.example.com` tells you what the port looks like from where the client actually is, and `-Pn` skips host discovery so a host that ignores ping is still scanned. Nmap defines the three answers precisely: `open` means an application on the target is listening; `closed` means the host replied but nothing is listening there; `filtered` means a firewall, filter or other network obstacle is blocking the probe so nmap cannot tell which. For RDP that distinction is the whole diagnosis — `closed` sends you to the server (service stopped, `fDenyTSConnections` set to 1, `PortNumber` moved), `filtered` sends you to the Windows Firewall rule or the cloud security group. Add `--reason` to see whether a reset came back or nothing did, and `-sV` to have nmap read the handshake. Scan only hosts you are responsible for.
| What you see | What it means |
|---|---|
The client spins, then shows the generic dialog listing three reasons: remote access is not enabled, the computer is turned off, or it is not available on the network | That dialog is a catch-all and names none of the three. Walk the chain in order instead: `qwinsta` on the target for an `rdp-tcp` row in `Listen`; `netstat -ano | findstr ":3389"` for a LISTENING row; `fDenyTSConnections` set to `0`; then the firewall. Each step eliminates one of the reasons the dialog refuses to choose between. |
The connection is refused immediately — the handshake gets an instant reset | Reachable host, nothing listening. `TermService` is stopped, Remote Desktop is disabled (`fDenyTSConnections` is `1`, possibly held there by a Group Policy that overrides the Settings checkbox), or `PortNumber` under `WinStations\RDP-Tcp` was changed so the listener is somewhere else entirely. `qwinsta` distinguishes the last case in one line: the listener exists but is not on the port you dialled. |
The client waits many seconds and then reports a timeout | Nothing answered at all, which is a drop rather than a refusal. Inbound Remote Desktop rules in Windows Firewall, a cloud security group or network security group, or a network ACL somewhere in between. A refusal is instant and a drop is slow, so the delay is itself the evidence; `nmap --reason` confirms it by reporting `filtered` rather than `closed`. |
Only one usage of each socket address (protocol/network address/port) is normally permitted | Windows error 10048, the local spelling of `EADDRINUSE`: something else grabbed 3389 before Remote Desktop Services started. Follow Microsoft's sequence — `netstat -ano | findstr ":3389"` for the PID, then `tasklist /svc | findstr "<pid>"` for the service behind it. If the owner is not `TermService`, the recommended fix is to move that other program, not to move RDP. |
netstat shows nothing on 3389 and the listener still cannot bind | The port is reserved rather than occupied, which is the Windows shape of a permission failure: there is no process to find. `netsh interface ipv4 show excludedportrange protocol=tcp` lists the dynamic-port blocks Hyper-V, WSL2, Docker Desktop and the Windows NAT service claim at boot. If 3389 falls inside one, restart the service that claimed it — killing a process will not help, because there is not one. |
The identity of the remote computer cannot be verified. Do you want to connect anyway? | Expected on a stock machine and not a network fault: you are being offered the RDP self-signed certificate the service generated for itself, which no client can validate. It means the session is encrypted but unauthenticated, so it proves nothing about which host is on the other end. Issue a real certificate for the listener if that matters — and if the warning appears on a host that previously had a trusted certificate, treat it as a finding rather than a nuisance. |
The session connects but is far laggier than the same link used to be | TCP 3389 is open and UDP 3389 is not. Microsoft's UDP transport extension uses the same port number for graphics and multimedia, and when it cannot come up the session silently falls back to TCP only. Check that the firewall rule covers both protocols rather than just TCP. |
IANA registers 3389 as `ms-wbt-server` (Microsoft's abbreviation for Windows-Based Terminal) for both TCP and UDP, and Windows really does use both. TCP carries the session; Microsoft's UDP Transport Extension takes graphics and multimedia over UDP, and its specification states that the default port for incoming UDP connection requests on the terminal server is 3389 as well. That is why a firewall rule that opens only TCP can leave you connected but visibly worse over a lossy link. On Windows the listener is not an ordinary program. It belongs to Remote Desktop Services — the `TermService` service — which is hosted inside a `svchost.exe`, so looking the PID up with plain `tasklist` names `svchost.exe` and tells you nothing useful; `tasklist /svc` names the service instead. Three registry facts decide the rest. `fDenyTSConnections` under `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server` is `0` when Remote Desktop is enabled and `1` when it is not, and a Group Policy can hold it at `1` whatever the Settings checkbox shows. `PortNumber` under `...\Terminal Server\WinStations\RDP-Tcp` is what actually chooses the port. And `qwinsta` tells you whether the `rdp-tcp` listener ever reached the `Listen` state, which is a different question from whether the service is running. Outside Windows the same number is used by third-party servers such as xrdp, so a Linux host answering on 3389 is running one of those, not a Microsoft component.
Do not put 3389 on the public internet. The history here is specific rather than theoretical: CVE-2019-0708, "BlueKeep", was a pre-authentication remote code execution flaw in Remote Desktop Services on Windows 7, Server 2008 and Server 2008 R2 — wormable, because it needed no credentials, no user interaction and no social engineering, only the ability to reach a vulnerable listener. Microsoft shipped fixes for out-of-support releases too, and noted that Network Level Authentication was a partial mitigation precisely because it forces authentication before the vulnerable code runs. Moving the listener with `PortNumber` is not a defence: Microsoft's own troubleshooting guidance explicitly does not recommend running RDP on another port, and a scanner that fingerprints services finds it regardless. Nor is the TLS warning you click past protection — the certificate is normally the machine's own RDP self-signed certificate, generated by the service and filed under Remote Desktop in the computer certificate store, so accepting it verifies nothing about the host you are about to type a domain password into. Put RDP behind a VPN, an RD Gateway or a jump host, restrict it by source address, require Network Level Authentication, and if identity matters, replace the self-signed certificate with an issued one the client will actually validate.
All of these run in your browser — nothing is uploaded.
Ports people usually end up checking in the same session as 3389.
Almost always permissions. lsof and ss list the socket but hide the owning process when it belongs to another user, so a server started by a system account, by a container runtime or by launchd shows up as an anonymous listener until you re-run the command with sudo. On Windows the opposite happens: netstat shows nothing at all, yet a bind still fails, which means the port sits inside a range that Hyper-V, WSL2 or Docker Desktop reserved at boot.
Look at what it is first. An orphaned dev server is safe to kill; a database with a write in flight is not, and neither is a system service that something else depends on. If the owner is a container, stop the container instead — killing the process the host sees only makes the runtime restart it. When the process is something you need, moving your own service to another port is the faster fix.
The listener bound to 127.0.0.1 rather than 0.0.0.0. The probes on every page print the bound address next to the process for exactly this reason: a loopback bind is reachable from the machine itself and from nowhere else, no matter how the firewall is configured. Many frameworks made that the default deliberately, so it is usually a flag rather than a bug. If the address is already 0.0.0.0 and it still fails, the next suspect is a host firewall or a cloud security group.
Barely. Internet-wide scanners sweep the whole port range continuously, so an unusual number buys hours, not safety, and it costs you every colleague who now has to remember it. It does cut the noise in your logs from opportunistic scans of the default port, which is a real if modest benefit. Authentication, a firewall that restricts source addresses, and not exposing the service at all are the measures that actually change the outcome.
Still stuck on port 3389? browse every port in the reference — or go back to the failure message you actually got, above.