EADDRINUSE again? This tool tells you who owns the port before you kill anything
EADDRINUSE again? This tool tells you who owns the port before you kill anything port-reclaim finds the process holding a busy dev port, shows you what it is, reclaims it when it's obviously yours, asks when it isn't,
EADDRINUSE again? This tool tells you who owns the port before you kill anything
port-reclaimfinds the process holding a busy dev port, shows you what it is, reclaims it when it's obviously yours, asks when it isn't, and refuses when it would be a mistake.
| Try it now | npx port-reclaim 3000 |
| Platforms | macOS ยท Linux ยท Windows ยท WSL2 (PowerShell + CMD) |
| Requires | Node.js 18+ |
| Runtime deps | 1 |
| Package size | ~21 KB |
| Telemetry | none โ no network calls, nothing stored |
| License | MIT |
Why this exists
Error: listen EADDRINUSE: address already in use :::3000
The reflex is lsof -i :3000 โ read a PID โ kill -9 it. Three things go wrong with that:
-
-9skips graceful shutdown โ anything mid-write to a socket, DB, or temp file gets truncated. - You're typing a number you read off a screen into a destructive command โ TCP and UDP both appear, PIDs repeat, and fatigue picks the wrong one.
- A PID is not a decision. "Something is on 3000" โ "that's the dev server you orphaned 40 minutes ago, not the Postgres."
The one check that fixes it
port-reclaim compares the working directory of the process with the directory you're standing in.
$ port-reclaim 3000 --list
Port 3000 is used by 'node' (PID 21836, '~/project', up 42m 11s) โ matches the current directory.
$ port-reclaim 3000
Released port 3000 from node (PID 21836).
| Situation | What happens |
|---|---|
| Process started from your directory | Reclaimed silently โ graceful signal first, forceful only if it survives |
| Different directory, or CWD unreadable | Shows name, PID, path, uptime โ asks you |
Docker, HTTP.sys, systemd
|
Refuses, even with --yes
|
| A database | Always asks, even if it shares your directory |
| Owner invisible to your user | Reports it as unclaimable instead of "free" |
| Windows process, you're in WSL2 | Names it, and explains why Linux can't see it |
$ port-reclaim 5432
Port 5432 is used by 'postgres' (PID 3352, '~/dbdata', up 3h 12m). Kill it? (y/N)
What it refuses to touch
The refusals are the point. They exist because the easy version of this tool is 20 lines of shell.
Docker & the kernel are off-limits.
Port 8080 is used by 'docker-proxy' (PID 16004, โฆ). Refusing to kill it โ it appears to be a Docker process โ stop the container instead (for example 'docker stop <container>').
Killing docker-proxy doesn't stop your container, it just leaves Compose in a worse state. PIDs 0โ4 are protected on the same logic: System/HTTP.sys on Windows, systemd on Linux. --yes cannot override either.
Databases never die quietly.
postgres,postmaster,mysqld,mariadbd,mongod,redis-server,memcached,etcdalways confirm โ even when running from inside your project, which is exactly the case a same-directory shortcut gets catastrophically wrong."I found nothing" is not "the port is free." When the owner is another user's or an elevated process,
lsofreturns nothing โ and empty output looks identical to a free port. So the tool checks the answer by briefly binding the port:
Port 3000 is in use by a process port-reclaim cannot see โ it may belong to another user, an elevated process, or a socket still closing.
It exits1, soport-reclaim 3000 && next devfails loudly instead of succeeding at nothing.WSL2 tells the truth. Under default NAT networking, Linux and Windows have separate port spaces, so a Windows-held port looks free to every Linux tool โ including this one until I tested it. It reaches across and says:
Port 4951 is free inside WSL, but a Windows tcp listener 'node.exe' (PID 19220) already holds it.
It reports rather than acts: a Windows PID passed toprocess.kill()would signal an unrelated Linux process.
Made for scripts
port-reclaim 3000 5173 8080 # several ports
port-reclaim --match '^node$' # every port held by a matching process name
port-reclaim --json # machine-readable, never terminates
port-reclaim # ports from config, no arguments
{ "port-reclaim": { "ports": [3000, 5173], "ignore": [5432] } }
A .reclaimignore file protects ports absolutely โ that beats every heuristic. --list inspects without acting. Exit codes: 0 free or released, 1 still in use, 2 bad input. Colour turns itself off when output isn't a terminal (--no-color, NO_COLOR, FORCE_COLOR all respected โ that's how the GIF above was recorded). There's a programmatic API too: discover, discoverListening, terminate, probe.
Fast, and measured
Whole-command wall clock on Windows 11 under Node 24, including Node's own startup, because that's the part people leave out of benchmarks:
| before | now | |
|---|---|---|
| answer "is port 3000 free?" | ~1.00s | ~0.10s |
| identify what's on a busy port | ~1.26s | ~0.41s |
| full reclaim of one process | โ | ~0.59s |
The "before" was a bug: discovery started PowerShell three times per port, and a cold powershell.exe costs ~0.7s. Now netstat decides whether anything is on the port, and PowerShell starts at most once, to name the processes actually found.
The honest limits
- Not a process manager or supervisor, and no substitute for
docker compose down. - UDP matching is a heuristic โ a UDP socket on a port can belong to a client, not a server.
- The free-port check binds momentarily, so a connection arriving in that instant can read as "in use". A race, not a flaw I'll paper over.
- It can't see other users' processes without your own elevated privileges.
-
0.x, one maintainer, ~52 tests across macOS/Linux/Windows ร Node 18/20/22/24. The programmatic API may still move โ read the changelog before upgrading.
Five real bugs are documented in that changelog โ a taskkill missing /F that reported success while the port stayed busy, a PowerShell cmdlet name that never existed, an uptime calculation always negative on Windows, process names silently wrong on macOS and Node 24 (which quietly disabled the protections above), and a WSL check that once blamed WSL's own relay process. All five are now covered by tests that bind a real socket and make the tool find it through the actual OS.
Get it
npx port-reclaim 3000
# or
npm install --global port-reclaim
npm โ port-reclaim ยท source โ princegoel0/port-reclaim
If it saves you one bad kill -9, it did its job. OS quirks I haven't hit are exactly what I want an issue about.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.