Skip to main content

portsight

CI PyPI Python License: MIT

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.exe from 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 ps and 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.

portsight compact report

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

portsight --port 3000

--port lists everything that touches the port:

  • host listeners, with PID, parent, and command line
  • Docker publishes
  • sockets in TIME_WAIT or CLOSE_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:

  1. No host socket is listening on it (TCP LISTEN or bound UDP).
  2. No Docker container publishes it.
  3. No Windows excluded port range covers it.
  4. For --check and --port only: 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

MIT

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)

Source distribution for portsight 2.0.0
File Size Uploaded
portsight-2.0.0.tar.gz 78.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for portsight 2.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page