You try to spin up your local server, and the terminal instantly throws the dreaded EADDRINUSE error. A ghost process is holding your target port hostage, bringing your development workflow to a sudden halt. Finding and terminating that exact process takes only a few seconds when you apply the right commands.

# Mac & Linux (one-liner)
kill -9 $(lsof -t -i:3000)
# Linux (fastest)
sudo fuser -k 3000/tcp
# Windows (PowerShell)
Stop-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess -Force

Why Does This Error Happen? (EADDRINUSE and TIME_WAIT)

Node.js and EADDRINUSE

Node.js applications often crash without cleanly shutting down their network connections. When you restart the application, the operating system still thinks the previous instance is actively listening on the port. The node process runs detached in the background. You must manually locate its Process ID (PID) and force the system to terminate it.

If you use Node.js regularly, knowing how to update Node.js also helps you avoid some version-related crash patterns that leave ports stranded.

The TIME_WAIT State

Sometimes your terminal shows the port as occupied, but you cannot find any active process attached to it. This happens because the operating system places the closed connection into a TIME_WAIT state to ensure all delayed network packets arrive safely. This mechanism usually lasts between 60 and 120 seconds. Waiting two minutes resolves it naturally, no kill commands needed.

Closing Ports on Mac and Linux

lsof and kill

The standard approach uses two steps. First, find which process is holding the port:

lsof -i :3000

The output shows the PID in the second column:

COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    12345  alice   24u  IPv6  0x...      0t0  TCP *:3000 (LISTEN)

Once you have the PID, send a termination signal:

kill 12345

That sends SIGTERM, asking the process to shut down cleanly. If it ignores the request, force-kill it with SIGKILL:

kill -9 12345

kill -9 drops the process immediately without any cleanup. Use it only when the polite approach fails. You can also skip the two-step process entirely by combining both into one line:

kill -9 $(lsof -t -i:3000)

ss Command for Fast Detection (Linux)

Modern Linux distributions prefer ss over the legacy netstat. It reads directly from the kernel and returns results significantly faster, especially on servers with many open connections:

ss -lptn 'sport = :3000'

The output includes the binary name, state, and PID in a single line. No need to cross-reference a separate process list. On Linux, fuser gives you an even shorter path:

sudo fuser -k 3000/tcp

If you run into shell path issues while using these commands, fixing your zsh: command not found: brew configuration usually resolves the problem.

Freeing Ports on Windows

Modern Method: PowerShell

PowerShell offers a more readable, pipeline-friendly alternative to the old netstat + taskkill combination:

# One-liner: find and kill the process on port 3000
Get-NetTCPConnection -LocalPort 3000 | Select-Object -ExpandProperty OwningProcess | Stop-Process -Force

Or as two steps if you want to inspect the process before killing it:

# Step 1: Find the PID
Get-NetTCPConnection -LocalPort 3000
# Step 2: Kill it
Stop-Process -Id <PID> -Force

Traditional Method: netstat and taskkill

For older environments or when PowerShell is unavailable, use Command Prompt:

netstat -ano | findstr :3000

Find the line with LISTENING and note the PID in the last column. Then kill it:

taskkill /PID 6789 /F

The /F flag forces termination, equivalent to kill -9 on Unix. If deep system services are blocking you, a full system restart from CMD clears the networking stack as a last resort.

Advanced Scenarios and Troubleshooting

Docker Container Conflicts

Port conflicts often happen between your host machine and a running Docker container rather than a local application. Before killing anything, check whether Docker is the real culprit:

docker ps --format "table {{.Names}}\t{{.Ports}}"

Sample output showing a container bound to port 3000:

NAMES        PORTS
my-app       0.0.0.0:3000->3000/tcp

If a container owns the port, stop it directly:

docker stop my-app

Running lsof on a Docker-heavy system often shows com.docker.backend as the process holding the port. That means the conflict is inside the container layer, and host-level kill commands will not help.

Zombie Processes and Permission Errors

A Permission Denied error means the process is running under a different user or as root. Add sudo:

sudo kill -9 12345

If the process comes back immediately after you kill it, it is managed by a service controller such as systemd (Linux) or launchd (Mac). You need to stop the service, not just the PID. To find the service name, look at the binary associated with your port:

# Find what binary is on the port
lsof -i :5432 -sTCP:LISTEN
# Then find its service unit
systemctl list-units --type=service | grep postgres

Stop the service:

# Linux
sudo systemctl stop postgresql
# Mac (Homebrew-managed service)
brew services stop postgresql

Watching the system logs in real time helps identify which service is respawning the process. The Linux tail command is the standard tool for that: sudo tail -f /var/log/syslog.

Port is Still Occupied After Killing?

Work through this in order:

1. Process still running? Run lsof -i :PORT again. If output appears, repeat the kill with sudo and -9.

2. No process, but port still blocked? It is in TIME_WAIT state. Wait 60-120 seconds and check again. Nothing to kill.

3. Process dies but immediately comes back? It is a managed service. Stop it with systemctl stop or brew services stop instead of targeting the PID.

4. Port blocked by Docker? Check docker ps. Stop the container, not the host process.

5. Still nothing works? You may have two instances of the same app running from different directories. Find all matching processes:

# Mac & Linux
ps aux | grep node
# Windows
Get-Process node

Kill all matching processes, then restart clean.

How to Verify the Port is Actually Free

Always confirm the kill worked before restarting your server:

# Mac & Linux
lsof -i :3000
# Linux alternative
ss -lptn 'sport = :3000'
# Windows PowerShell
Get-NetTCPConnection -LocalPort 3000

An empty output means the port is free. Start with kill (graceful). If the port stays occupied, use kill -9. If the process keeps coming back, stop it at the service level, not the process level.