Skip to content

Repository files navigation

whoseport

Whose port is it anyway?

One command to see which process is squatting on a port — and free it if you want. Pure Python standard library: no psutil, no compiled wheels, no Node, no Rust toolchain.

CI Release Python License: MIT OS

English | 简体中文

whoseport demo: see who is on port 8080, then free it

The problem

Address already in use. EADDRINUSE. bind: Only one usage of each socket address.

You know the drill — and you have googled this exact incantation more times than you'd like to admit:

Step Windows macOS / Linux
1. find the PID netstat -ano | findstr :8080 lsof -i :8080 (was it -i:8080? -nP?)
2. find out what it is tasklist | findstr 23456 ps -p 23456
3. kill it taskkill /F /PID 23456 kill -9 23456

Three commands, two syntaxes to remember, and on non-English Windows the netstat output is localized so half the copy-pasted one-liners break anyway.

The fix

$ whoseport 8080
PORT  PROTO  STATE     PID  PROCESS  ADDRESS
8080  tcp    LISTEN  31425  node     0.0.0.0
     └─ node server.js --port 8080

$ whoseport 8080 --kill
About to terminate 1 process:
  PID 31425  node  (port 8080)
Proceed? [y/N] y
✓ PID 31425 (node): SIGTERM sent
✓ port 8080: free

That's it. It shows you who is on the port before anything gets killed.

Install

pip install whoseport        # or: pipx install whoseport

Run it without installing:

uvx whoseport 8080           # uv
pipx run whoseport 8080      # pipx

Straight from GitHub (always works, even before a PyPI release propagates):

pip install git+https://github.com/hc-ui/whoseport
# or grab the wheel from the Releases page

Requires Python ≥ 3.9. Nothing else — the dependency list is empty.

Usage

$ whoseport                  # what's listening on this machine?
$ whoseport 8080             # who is using port 8080?
$ whoseport 3000-3010 5432   # multiple ports and ranges
$ whoseport 8080 --kill      # terminate the owner (asks first)
$ whoseport 8080 -k -y -f    # no questions, SIGKILL (CI-friendly)
$ whoseport 8080 --wait 60   # block until the port is free (CI, restart loops)
$ whoseport 8080 --json      # machine-readable, for scripts and AI agents
$ whoseport 8080 --tcp       # TCP only (--udp for UDP only)
$ whoseport 8080 --all       # every socket, incl. established connections

Busy ports stay readable — connection noise is collapsed, listeners stay front and center:

$ whoseport 7897
PORT  PROTO  STATE     PID  PROCESS           ADDRESS
7897  tcp    LISTEN  34040  verge-mihomo.exe  0.0.0.0
7897  tcp6   LISTEN  34040  verge-mihomo.exe  [::]
7897  udp    -       34040  verge-mihomo.exe  0.0.0.0
  + 165 ESTABLISHED, 77 TIME_WAIT connection(s) from 1 process(es) on the same port(s) - use --all to list them

Exit codes

Code Meaning
0 found something (or killed it successfully)
1 nothing is using the port(s)
2 usage / platform error
3 kill failed or the port is still busy

So whoseport 8080 || echo free just works in scripts.

Why not ...?

whoseport killport (Rust) kill-port (npm) psutil scripts raw netstat / lsof
Install pip / pipx / uvx brew / cargo needs Node needs a compiled dep built-in
Runtime deps none (pure stdlib) none (binary) Node runtime psutil wheel
Shows who before killing ✓ table + cmdline dry-run flag partial write it yourself arcane flags
Safe by default ✓ confirm + SIGTERM first ✗ SIGKILL by default ✗ kills immediately up to you manual
Explains TIME_WAIT raw rows
Locale-proof on Windows ✓ native API ✗ parses netstat text ✗ (LISTENING vs 侦听)
JSON output write it yourself
Verifies the port is actually free after killing write it yourself manual

If you live in the Rust or Node ecosystem, those tools are great. whoseport is for everyone who already has Python — which, if you fight with ports 8000/8888/5000, you probably do.

How it works

No psutil. Each platform gets the same treatment the native tools use, minus the parsing hazards:

  • Linux — reads /proc/net/{tcp,tcp6,udp,udp6} and maps socket inodes to PIDs via /proc/*/fd (exactly what ss -p does). No subprocess at all.
  • Windows — calls GetExtendedTcpTable / GetExtendedUdpTable from iphlpapi.dll via ctypes — the same API netstat itself uses. Locale-proof by construction, no console codepage headaches. Elevated process names are recovered through one batched tasklist call.
  • macOS — uses the lsof that ships with macOS, in -F machine-readable field mode (not the human table, which shifts between versions).

Safety

--kill is designed to be hard to regret:

  • Shows exactly what will die and asks for confirmation (skip with --yes).
  • Sends SIGTERM first; SIGKILL only with --force. (On Windows, TerminateProcess is the only reliable option for console apps — equivalent to taskkill /F — and the docs say so instead of pretending otherwise.)
  • Refuses to touch PID 0/1/4, itself, or its parent shell.
  • Re-checks the port afterwards and tells you whether it is actually free, including lingering TIME_WAIT sockets that clear on their own.

Scripting & AI agents

--json emits a stable document — handy for shell scripts and for LLM agents that keep hitting EADDRINUSE in dev loops:

{
  "whoseport": "0.1.0",
  "platform": "win32",
  "query": [8080],
  "sockets": [
    {
      "proto": "tcp", "ip_version": 4, "local_addr": "0.0.0.0",
      "local_port": 8080, "state": "LISTEN", "pid": 31425,
      "process": "node", "cmdline": "node server.js --port 8080"
    }
  ]
}
# block until port 8080 is free (exit 0), or give up after 60s (exit 3)
whoseport 8080 --wait 60

# free a port in CI before starting the dev server
whoseport 3000 --kill --yes --force || true

# one-liner check: is the port free right now?
whoseport 8080 >/dev/null || echo "free"   # exit 1 = free

Use as a library

The same engine is importable — still zero dependencies:

import whoseport

for sock in whoseport.collect(ports={8080}):
    print(sock.pid, sock.process, sock.state, sock.local_addr)

FAQ

Some rows show ? as PID on Linux. Sockets of other users are visible, but their owners aren't (same as netstat). Run with sudo to see everything.

A TIME_WAIT row is shown with - as the process. That socket has no live owner — the kernel keeps it briefly after a connection closes. It clears on its own, and servers that set SO_REUSEADDR can bind right through it. whoseport tells you this instead of letting you hunt for a process that doesn't exist.

On Windows the process name shows but the path doesn't. The owner runs elevated. Names come from tasklist (works from any terminal); full paths need an elevated terminal.

The port is owned by docker-proxy / com.docker.backend / vpnkit. Killing that process won't help — stop the container instead: docker psdocker stop <container>.

PID 4 / System owns port 80 or 443 on Windows. That's http.sys. Don't kill it — find the reservation with netsh http show servicestate or stop the service using it (often IIS or the "World Wide Web Publishing Service").

Development

git clone https://github.com/hc-ui/whoseport
cd whoseport
pip install -e ".[dev]"
pytest            # unit + real end-to-end tests (they bind sockets and kill child processes)
ruff check src tests

CI runs the suite on Linux, macOS and Windows across Python 3.9–3.14.

License

MIT

About

Whose port is it anyway? One command to see which process is using a port - and free it. Zero deps, pure stdlib. | 一条命令查出端口被哪个进程占用并释放:零依赖、跨平台,免记 netstat/taskkill 三连咒语

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages