← All Articles

EADDRINUSE: Address Already in Use — Port Conflict Fix for Node.js

What Does This Error Mean?

Error: listen EADDRINUSE: address already in use :::3000 means your server tried to bind to port 3000, but another process is already listening there. The operating system allows only one process per port (per protocol), so the second server gets rejected. It's not a bug in your code — it's a conflict with something already running.

Find the Process Holding the Port

First, identify the culprit. On macOS/Linux:

lsof -i :3000
# or
lsof -nP -iTCP:3000 -sTCP:LISTEN

# Shows: COMMAND  PID  NAME
# node     4521  *:3000 (LISTEN)

On Windows:

netstat -ano | findstr :3000
# Then kill the PID:
taskkill /PID 4521 /F

Kill It or Let It Go

If the process is a leftover dev server (the classic case — you Ctrl+C'd the terminal but the process survived, or another terminal still has it running), kill it by PID:

kill 4521          # macOS/Linux, graceful
kill -9 4521       # only if graceful fails

If the process is something you need (a database, another app), don't kill it — change your app's port instead: PORT=3001 npm run dev, or set the port in your .env file. Many frameworks read a PORT environment variable by default.

Why Dev Servers Leave Zombie Processes

This error appears most often after you've restarted a dev server a few times. The usual causes: (1) the terminal was closed while the server ran — the process keeps running detached; (2) a crashed server that didn't release the port (rare on modern OSes, but Node's cluster mode can leave children orphaned); (3) two terminal tabs both running npm run dev. Rule of thumb: if you're sure nothing should be on that port, lsof will show you exactly what's there, and 9 times out of 10 it's an old copy of your own server.

Automatic Port Handling

For dev workflows, make the port resilient instead of manual: (1) use a process manager like nodemon that kills its own children on restart; (2) pick a different default port for each project (3000, 3001, 3002...) so projects don't collide; (3) or handle it in code for tests:

const server = app.listen(0)  // port 0 = OS picks a free port
console.log(server.address().port)  // actual port

Port 0 is the standard trick for test suites that need a guaranteed-free port.

When You Actually Need SO_REUSEADDR

SO_REUSEADDR lets a new process bind to a port in TIME_WAIT (the state a port lingers in after a connection closes, typically 60 seconds on macOS/Linux). Node enables it by default for server sockets in most versions, so you rarely need to touch it. The one case where it helps: rapid restart loops in dev, where the port is in TIME_WAIT from the previous run. If you're hitting EADDRINUSE on every restart right after killing the process, wait 60 seconds or use server.listen(port, '0.0.0.0') explicitly — the address binding can also matter when localhost resolves to IPv6 (::1) while the server bound to IPv4 (127.0.0.1). For that variant, the ECONNREFUSED guide has the IPv4/IPv6 mismatch fix.

The 60-Second Fix

1. lsof -i :3000 (replace 3000 with your port). 2. Read the PID. 3. kill <PID>. 4. Restart your server. That's the whole process for 95% of cases — the other 5% are either a real service you shouldn't kill (change your port) or TIME_WAIT (wait a minute). Never change your code to fix this; the code is fine, the port is occupied.