portsight
See who owns every local port, and which ports are actually free.
portsight answers the questions netstat makes you work for:
- What is on port 3000? The process, its full command line, and its parent, so you can tell one
node.exefrom another. - Why can't I bind port 50010 when nothing is listening? Windows excluded port ranges (Hyper-V, WinNAT, WSL2) block binds silently. portsight shows them and tells you how to clear them.
- Why does netstat miss my Docker containers? Docker Desktop forwards published ports without a host socket. portsight reads
docker psand counts those ports as taken. - Give me a free port. One random free port, the N lowest, or a contiguous block. Each one is checked against listeners, containers, and exclusions.
It runs on Windows, Linux, and macOS. The Windows-specific checks (excluded ranges, netsh dynamic pool) light up on Windows, and everything else works everywhere.
Install
portsight needs Python 3.10 or later. Install it as an isolated command-line tool:
pipx install portsight
# or
uv tool install portsight
Plain pip install portsight works too. To run the latest code from source, see CONTRIBUTING.md.
Docker is optional. When the docker CLI is on PATH and the daemon is running, published container ports are included automatically.
Quick start
portsight # full report
portsight -c # compact report
portsight --port 3000 # who owns 3000, and why can't I bind it?
portsight --check 3000 # prints "free" or "in use"; exit code 0 or 1
portsight -r # one random free port
portsight -k 3000 # kill the process on 3000 (asks first)
Run portsight --help for every option, grouped by task, with examples and exit codes.
Usage
Explain one port
portsight --port 3000
--port lists everything that touches the port:
- host listeners, with PID, parent, and command line
- Docker publishes
- sockets in
TIME_WAITorCLOSE_WAIT(a common reason a just-stopped server can't restart) - the Windows exclusion that covers the port, if any
When an exclusion is the cause, portsight prints the fix:
net stop winnat & net start winnat
Run that from an elevated shell. Existing WSL2 and Docker port forwards drop until they republish.
Use it in scripts
The allocation and check modes print bare values on stdout and signal results through the exit code. Diagnostics go to stderr.
# Start a dev server only if its port is free
portsight --check 3000 && npm run dev
# Pick a free port in a window (bash / zsh)
PORT=$(portsight -r --from 4000 --to 4999)
# Three adjacent ports for a multi-service stack
portsight --take 3 --contiguous --from 7000
# PowerShell
$port = portsight -r --from 4000 --to 4999
Add --json to any mode for structured output. See docs/json.md for the schema.
portsight --json | jq '.listening[] | select(.port == 3000) | .cmdline'
Check a project's ports
Tell portsight which ports a project needs. It reports whether each one is free, held by a host process, published by docker, held by a lingering socket, or reserved by a Windows exclusion.
portsight --expect "3000 web, 5432 db, 8080-8082 api"
Alternatively, commit a .portsight file and let --project read it along with your docker-compose.yml or compose.yaml:
# .portsight
3000 web
5432 db
8080-8082 api
portsight --project
See docs/project-files.md for the full format.
Free a port
portsight -k 3000 # terminate the host process(es) on 3000
portsight --stop 5432 # docker stop the container publishing 5432
portsight --stop-container web # docker stop by name
These commands show what they will act on and ask first. In scripts, -y skips the prompt. If there's no terminal to answer and you didn't pass -y, they decline instead of hanging. --kill refuses to touch PID 0, 1, 4 (the Windows System process), and itself. It also only kills the processes you saw in the preview.
Track changes over time
portsight --snapshot before.json
# ...install something, start some services...
portsight --diff before.json # exit 1 if listeners changed
portsight --watch # live: report once, then timestamped changes
portsight --watch 5 --json # JSON Lines, one document every 5 seconds
Diffs ignore the churn that outbound traffic leaves behind: client sockets and UDP binds inside the OS ephemeral pool. That makes --diff reliable as a "did anything start listening?" check.
Filter the report
portsight --exposed # hide loopback-only binds
portsight --process node # rows whose name, path, command, or parent contains "node"
portsight -t # TCP only
portsight --no-docker # skip Docker discovery
Filters only change what's shown. Free ranges always count every occupied port, so a hidden listener never looks bindable.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success. --check / --port: the port is free. --diff: no changes. |
1 |
--check / --port: in use. --diff: something changed. -r / --take: not enough free ports. --kill / --stop: the port was not freed. |
2 |
Usage error, unreadable snapshot, or invalid --expect / .portsight. |
130 |
A confirmation prompt was declined. |
How "free" is decided
A port is free when all of the following are true:
- No host socket is listening on it (TCP
LISTENor bound UDP). - No Docker container publishes it.
- No Windows excluded port range covers it.
- For
--checkand--portonly: no socket in a bind-blocking state (TIME_WAIT,CLOSE_WAIT, and similar) sits on it.
Ports inside the OS dynamic (ephemeral) pool still count as free. You can bind them, but outbound connections may grab them, so -r prefers the 1024–49151 band unless you pass --from / --to.
docs/how-it-works.md covers the data sources on each OS.
Platform notes
| Windows | Linux | macOS | |
|---|---|---|---|
| Listeners, PIDs, command lines | ✓ | ✓ (other users' PIDs need root) | ✓ (needs sudo) |
| Docker published ports | ✓ | ✓ | ✓ |
| Excluded / reserved ranges | ✓ (netsh) |
n/a | n/a |
| Ephemeral pool | ✓ (netsh) |
✓ (/proc) |
✓ (sysctl) |
When the OS refuses to show the socket table, portsight says the scan was incomplete. It never reports "nothing is listening" in that case. See docs/troubleshooting.md.
Contributing
Bug reports and pull requests are welcome. See CONTRIBUTING.md for the dev setup, and CHANGELOG.md for release history.
License
Metadata
Release files for portsight 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| portsight-2.0.0.tar.gz | 78.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| portsight-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 145.6 kB
Release files / portsight-2.0.0.tar.gz
| Download URL | portsight-2.0.0.tar.gz |
|---|---|
| Size | 78.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0bda847af97d8ee098fa8c6a1f2f33662830f0687b0951092f81aadc00dd27f4
|
|
BLAKE2b-256 checksum How to use checksums |
cc4e7c1819403dc91204fbdbb593fe00ac769a091ccc0a330ecadbef1e8dcfc8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / portsight-2.0.0-py3-none-any.whl
| Download URL | portsight-2.0.0-py3-none-any.whl |
|---|---|
| Size | 67.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c86a286fdfac3476c121f42c262dcedcce130e2fe41876fe3f909f04f9721178
|
|
BLAKE2b-256 checksum How to use checksums |
389b46286305c4248cd4c9bbe9074331af30d23ba510959e7178bf9c2f3c0abe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log