Toolshed

A growing library of browser tools and deep technical guides for IT professionals.

← All guides

Diagnosing "address already in use" and finding what's holding a port

4 min read

A server won't start, and the error is some variant of EADDRINUSE — "address already in use," "bind failed," or Node's Error: listen EADDRINUSE: address already in use. The fix is always the same two steps: find out what's actually holding the port, then decide whether to stop it or pick a different port. Here's how to do the first step correctly, and why it sometimes shows nothing even though the error is real.

What's actually happening

Starting a TCP server on a port is a bind() system call, and only one process can bind a given IP/port combination at a time (with one exception, SO_REUSEADDR, covered below). If something else already holds that port, bind() fails with errno 98 on Linux (EADDRINUSE) — confirmed directly: binding a socket, then attempting a second bind on the same port from another process, fails with exactly errno: 98, EADDRINUSE, "Address already in use". Whatever framework or runtime you're using surfaces that same underlying errno under its own wrapper message.

Finding the culprit

ss is the modern, actively-maintained tool for this on Linux (netstat still works on many systems but is deprecated upstream in favor of ss):

ss -ltnp | grep :8080
# LISTEN 0  128  0.0.0.0:8080  0.0.0.0:*  users:(("node",pid=41213,fd=3))

-l (listening sockets only), -t (TCP), -n (numeric ports, don't resolve service names), -p (show the owning process — requires root or matching-user privileges to see the PID for a process you don't own). The users: field gives you the exact PID and process name.

lsof works the same way and is often more familiar on macOS, where it's the standard tool (macOS's netstat/ss situation is different from Linux's):

lsof -i :8080
# COMMAND   PID  USER   FD   TYPE  NODE NAME
# node    41213 jacks    3u  IPv4  TCP  *:8080 (LISTEN)

On Windows: netstat -ano | findstr :8080 gives the PID in the last column, then tasklist /fi "PID eq <pid>" to see what process that actually is.

Why it sometimes shows nothing, and the port is still busy

If neither command shows anything holding the port but a new process still can't bind it, the port is very likely in TIME_WAIT — a lingering, half-closed socket left over from a connection that already ended. After a TCP connection closes, the side that initiated the close holds the socket in TIME_WAIT for a fixed interval (commonly around 60 seconds, platform-dependent) specifically to catch any stray packets still in flight from the old connection. The process that owned it may already be completely gone — TIME_WAIT sockets show up in ss -tan (without -l, since they're not listening sockets anymore) but typically without an owning PID, because there usually isn't one anymore.

ss -tan | grep :8080
# TIME-WAIT  0  0  127.0.0.1:8080  127.0.0.1:52344

This is also exactly why a server that was just restarted sometimes fails to bind immediately, then succeeds a minute later with no code change at all — the old socket simply hadn't finished its TIME_WAIT window yet.

The fix, in order

  1. A real process is listed (via ss -ltnp or lsof -i): that's your answer — either stop that process, or configure your new one to use a different port. Killing a process you don't recognize just to free the port is worth pausing on first — check what it actually is before ending it.
  2. Nothing's listed, but the bind still fails: check for a lingering TIME_WAIT socket with ss -tan (no -l). This resolves itself; waiting it out is the correct fix, not a workaround.
  3. A server needs to restart immediately without waiting out TIME_WAIT: this is what SO_REUSEADDR (or equivalent — most frameworks expose this as a simple boolean option) exists for. It explicitly tells the kernel "let me bind this port even if a previous socket on it is still in TIME_WAIT" — the reason some servers restart instantly and others don't is almost always whether this flag is set.

Sources: Linux bind(2) / errno(3) man pages (EADDRINUSE = errno 98); ss(8) and lsof(8) man pages; TCP TIME_WAIT behavior per RFC 9293 (TCP specification, §3.3.2); SO_REUSEADDR behavior per socket(7). The errno 98 / ss/lsof output shown above was reproduced directly, not illustrative.