Debug the runtime
Diagnose processes and ports
Identify the listener, address, protocol, process, and duplicate instance behind a port failure.
10 minute lesson
“Port in use” and “connection refused” are opposite clues. EADDRINUSE means something already holds the address you tried to bind. ECONNREFUSED means you reached the machine and nothing was listening there. One indicates an existing bind; the other often indicates no listener at the reached address. Both get diagnosed the same way: find out who is actually listening, and where.
Find the listener
Inspect TCP port 3000:
ss -lntp | grep :3000
# macOS: lsof -nP -iTCP:3000 -sTCP:LISTEN
A typical line:
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=41763,fd=19))
That answers three questions at once. Something is listening on 3000. It is a node process, PID 41763. And it bound 127.0.0.1 — loopback only.
Match PID to command and configuration:
ps -p 41763 -o pid,user,command
The command line often reveals the surprise: yesterday’s dev server you forgot about, or a second copy of the same app started by a process manager that auto-restarts it.
Loopback vs. every interface
The bind address explains a whole family of “works here, refused there” bugs. Check whether it listens on loopback, one interface, or every interface:
127.0.0.1:3000 reachable only from the same machine
0.0.0.0:3000 reachable on every interface
A server bound to 127.0.0.1 inside a container or on a remote host answers curl locally and refuses everyone else. Nothing crashed — the listener and the client just disagree about which address to meet on.
If ss shows no listener at all, “connection refused” is fully explained: the service crashed, is still starting, or listens on a different port than the client was told. Go read that service’s logs and its effective configuration.
Before you reach for kill
Do not kill an unknown process just to free a port. EADDRINUSE plus an unfamiliar PID means something is running that you do not yet understand, and it may be serving real traffic. Identify owner and service impact first, then stop it through its manager — systemctl stop, your process manager — rather than a bare kill -9 that its supervisor will immediately undo anyway.
Lesson completed