Node.js Error: EADDRINUSE — Address Already In Use
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:1817:16)
at listenInCluster (node:net:1865:12)
at Server.listen (node:net:1953:7) {
code: 'EADDRINUSE',
errno: -98,
syscall: 'listen',
address: '::',
port: 3000
}
EADDRINUSE is the OS telling you that two processes can’t bind the same TCP address+port. It’s a clean, deterministic error: there’s exactly one fix (free the port), and exactly one long-term prevention (handle shutdown signals). The hard part is that the offending process is often invisible — your terminal closed but the Node process didn’t.
The first command in any EADDRINUSE debug session is lsof -i :<port> (or the Windows equivalent). Once you know who’s holding the port, the rest is mechanical.
Why this happens
- Previous dev-server process still running. Most common locally — you killed the terminal tab but the Node process kept running, or a hot-reload tool (`nodemon`, `tsx watch`) spawned a child that didn't get cleaned up. The shell shows you back at a prompt but `lsof` shows the port still bound.
- Another app on your machine uses the same port. Port 3000 is shared by Next.js, Create React App, Rails dev server, and dozens of others. Port 8080 is shared by Tomcat, dev servers, Jenkins. Two services configured for the same port collide on the second one to start.
- Multiple cluster workers binding the same port without cluster module. If you `node app.js` twice (or accidentally launch two replicas locally) and your app calls `server.listen(3000)`, the second one fails. The Node `cluster` module + `SO_REUSEPORT` is what allows multiple workers to share a port; without it, you get EADDRINUSE.
- Lingering TIME_WAIT socket from the previous bind. Less common with modern Node (which sets `SO_REUSEADDR` by default), but possible after a hard kill: the TCP stack keeps the socket in TIME_WAIT for ~60s. New `listen()` fails with EADDRINUSE until the wait expires.
- Container port mapping conflict. `docker run -p 3000:3000` fails if anything on the host already binds 3000 — Docker can't map the port. The error message comes from Docker, not Node, but reads similarly.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Find and kill the process holding the port
`lsof` on Mac/Linux, `netstat` on Windows. Both give you the PID; kill it and restart your dev server.
# Mac / Linux
lsof -i :3000
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# node 12345 you 25u IPv6 0x... 0t0 TCP *:hbci (LISTEN)
kill -9 12345
# Or in one line:
kill -9 $(lsof -t -i :3000)
# Windows (PowerShell or cmd)
# netstat -ano | findstr :3000
# taskkill /PID 12345 /F
2. Handle SIGTERM/SIGINT so the server shuts down cleanly
The right long-term fix: register signal handlers that close the server before the process exits. Containers send SIGTERM on stop; CTRL-C sends SIGINT. Closing the server frees the port immediately.
import http from 'node:http';
const server = http.createServer((req, res) => {
res.end('hello');
});
server.listen(Number(process.env.PORT ?? 3000));
function shutdown(signal: string) {
console.log(`received ${signal}, shutting down…`);
server.close((err) => {
if (err) {
console.error('error closing server', err);
process.exit(1);
}
process.exit(0);
});
// Hard exit if close hangs.
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
3. Use `PORT=0` to let the OS pick a free port
In tests and ephemeral dev scenarios, binding to port 0 tells the OS "give me any free port." Read the actual port back from `server.address()`. Eliminates port collisions in parallel test runs.
import http from 'node:http';
const server = http.createServer((req, res) => res.end('ok'));
server.listen(0, () => {
const addr = server.address();
if (addr && typeof addr === 'object') {
console.log(`test server on http://localhost:${addr.port}`);
}
});
4. Configure your dev tools not to leak processes
`nodemon`, `tsx watch`, and `next dev` should kill their child on restart, but bugs (and weird shells) leak. Add an `npm run kill-port` script and call it before `dev`. On macOS use `pgrep -f node | xargs kill -9` cautiously.
5. Pick a different port (last resort)
`PORT=3001 npm run dev` if you genuinely have two services that both want 3000 and neither can move. Don't make this a habit — it papers over the cleanup bug.
Detection and monitoring in production
In CI, EADDRINUSE in a parallel test suite is almost always two test files binding the same hardcoded port. Always use port 0 in tests. In production: EADDRINUSE during boot indicates the previous container didn't release the port (shared host-network mode) or your orchestrator restarted the pod faster than TIME_WAIT cleared. Set `tcp_tw_reuse=1` on Linux hosts that hit this often, or add a 30s readiness delay.
Related errors
- postgresECONNREFUSEDYour application tried to open a TCP connection to Postgres and the OS rejected it — Postgres isn't listening on the host:port you specified, or a firewall blocked the connection.
- nodejsERR_REQUIRE_ESMYour CommonJS file used `require('some-package')` but the package is now ESM-only (its `package.json` has `"type": "module"` or `"exports"` only points to `.mjs`). Node's CJS loader can't synchronously load an ESM module.
- nodejsheap_out_of_memoryV8's old-generation heap filled up and the garbage collector couldn't free enough space, so V8 aborts the process with a fatal allocation failure. Default heap is ~4GB on 64-bit; long-lived references (caches, listeners, closures, big arrays) prevent reclamation.
- nextjsmodule_not_foundThe Next.js build (webpack/Turbopack) tried to resolve an import path and couldn't find it. Either the package isn't installed, the relative path is wrong, a TypeScript path alias isn't mirrored in `tsconfig.json` and `next.config.js`, or the file's case differs between disk and import (Linux is case-sensitive, macOS isn't).
- nextjsFUNCTION_INVOCATION_TIMEOUTA Vercel serverless function (a Next.js API route, server action, or `getServerSideProps`) didn't return a response within the plan's max execution time — 10s on Hobby, 60s on Pro, 900s on Enterprise. Vercel kills the invocation and returns 504.
Frequently asked questions
How do I find which process is using port 3000? +
Why does EADDRINUSE happen even after I quit my terminal? +
Can I make my Node server listen on the same port as another instance? +
I get EADDRINUSE even though `lsof` shows nothing on the port. +
Why does docker run get EADDRINUSE but no Node process is running? +
Should I catch EADDRINUSE and pick a different port? +
Does PM2 / Forever / nodemon prevent EADDRINUSE? +
Can EADDRINUSE happen with Unix sockets? +
When to escalate to Node.js support
EADDRINUSE is virtually always a local issue — the OS report is unambiguous. Escalate to infra only if (a) on a shared host, multiple containers consistently collide despite different configured ports (could be a host-network-mode misconfiguration), or (b) Kubernetes pods get EADDRINUSE on `hostPort` mappings (the port is reserved on the node — change the node selector or the port). Otherwise, find and kill the conflicting process.