Skip to content
shellmap

Find which process is listening on a port

Identify the PID and process name bound to a local TCP/UDP port — for debugging "address already in use" errors, auditing what's exposed, and killing a zombie server holding port 3000.

How to find which process is listening on a port in each shell

Bashunix
ss -tlnp sport = :3000

`ss` (modern, iproute2) is the Linux default since the early 2010s — `netstat` is deprecated but still ubiquitous. Flags: `-t` TCP, `-l` listening, `-n` numeric ports (skip DNS lookups), `-p` show process. `sport = :3000` filters to source port 3000 (the listening port). Output: `LISTEN 0 511 *:3000 *:* users:(("node",pid=12345,fd=21))`. UDP: swap `-t` → `-u`. For "what's listening on ANY port": `ss -tlnp`. macOS doesn't have `ss` — use `lsof`: `lsof -nP -iTCP:3000 -sTCP:LISTEN` (the `-nP` skips name/port resolution for speed; `-sTCP:LISTEN` is the state filter). Both `ss -p` and `lsof` need root for some process names (kernel hides PIDs from non-owners).

Zshunix
lsof -nP -iTCP:3000 -sTCP:LISTEN

macOS default shell → `lsof` is the macOS-native answer (`ss` is Linux-only). `lsof -i :3000` lists EVERYTHING (TCP+UDP, listeners+established connections); `-iTCP:3000 -sTCP:LISTEN` narrows to TCP listeners only. Output columns: `COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME` — read `PID` and `COMMAND`. To kill it: `lsof -ti:3000 | xargs kill -9` (the `-t` terse mode prints just PIDs, pipe-friendly). On Linux, `lsof` is also available but `ss` is faster (lsof scans /proc, ss queries netlink).

Fishunix
ss -tlnp sport = :3000

Same external tools as bash. Fish chaining for "kill whatever is on port 3000": `lsof -ti:3000 | xargs kill -9` works identically (fish pipes Unix-style). For repeated lookups, a fish function: `function port-pid; lsof -ti:$argv[1]; end` lets you `port-pid 3000` and get the PID. Fish-specific: `ss` output isn't natively parsed by fish, but `string split` + `string match` work on the lines if you need scripted extraction.

PowerShellwindows
Get-Process -Id (Get-NetTCPConnection -LocalPort 3000 -State Listen).OwningProcess

The pwsh-native answer (Windows only — `Get-NetTCPConnection` is a Windows cmdlet, missing on Linux/macOS pwsh). `Get-NetTCPConnection -LocalPort 3000 -State Listen` returns the PID in `OwningProcess`; pipe into `Get-Process -Id` to resolve the process name + path. To kill it: `Stop-Process -Id (Get-NetTCPConnection -LocalPort 3000 -State Listen).OwningProcess -Force`. On Linux/macOS pwsh, fall back to `lsof` or `ss` directly. Admin rights NOT required to see PID + name on Windows (different from cmd `netstat -b` which needs admin); system-process names may still come back null without admin.

cmd.exewindows
netstat -ano | findstr ":3000"

`netstat -ano` lists all connections with addresses (`-a`), numeric format (`-n`), and owning PID (`-o`). `findstr ":3000"` filters to lines containing the port — but matches BOTH source AND destination columns, so for "ONLY listening on 3000": `netstat -ano | findstr "LISTENING" | findstr ":3000"`. Last column is the PID — resolve to name: `tasklist /FI "PID eq 12345"`. The classic Windows admin one-liner. `netstat -b` (note `-b`) shows the binary name directly but REQUIRES ADMIN — most newcomers hit "The requested operation requires elevation". Without admin, use `-o` and resolve via `tasklist`.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **"Address already in use" debugging flow**: (1) Identify what holds the port — one of the commands above. (2) Decide: is it a stale instance of your own process (safe to kill) or something else (don't kill, change YOUR port). (3) If kill: `kill -9 <PID>` (Unix), `Stop-Process -Force -Id <PID>` (pwsh), `taskkill /F /PID <PID>` (cmd). (4) Wait for `TIME_WAIT` to clear (~60s on most kernels) before rebinding — OR use `SO_REUSEADDR` in your server config to skip the wait. Node.js / Python / Go all default to NOT setting `SO_REUSEADDR`; explicitly opt in if your dev loop restarts the same port frequently.
  • **Docker / WSL2 port namespacing**: a container or WSL2 instance can `bind` 0.0.0.0:3000 INSIDE its network namespace without your host seeing the port at all. `ss -tlnp` on the host won't show the container's listener (different netns). To inspect INSIDE a Docker container: `docker exec <id> ss -tlnp` or `nsenter -t <pid> -n ss -tlnp`. WSL2 uses its own Linux VM — `ss -tlnp` on Windows host won't show WSL2 listeners; run from inside WSL. For "port published via -p host:container", `docker port <id>` shows the host↔container mapping; check both sides if a request isn't reaching the app.
  • **Process-name resolution and admin/root**: on Linux, `ss -p` and `lsof` need root to show OTHER users' process names (kernel hides PIDs cross-user). On Windows, `netstat -b` needs admin; `netstat -o` + `tasklist` (the workaround) does NOT. Pwsh `Get-NetTCPConnection` shows OwningProcess (PID) without admin, but resolving `Get-Process` for that PID may fail with `Access is denied` for system processes. On macOS, `lsof` similarly hides PIDs of other users — `sudo lsof` is the universal escape.
  • **`ss` vs `netstat` performance**: `netstat -tlnp` scans `/proc/net/tcp` line by line — O(connections), slow on busy servers (10k+ connections). `ss` queries the kernel via netlink (NETLINK_INET_DIAG) — orders of magnitude faster, designed for high-connection-count hosts. Use `ss` if available (every Linux from 2014+). `netstat` deprecation note: `iproute2` ships `ss` / `ip` as replacements for `netstat` / `ifconfig` — keep your scripts portable by preferring `ss`.

Related commands

Related tasks