Fix "Error: listen EADDRINUSE: address already in use :::3000" after a crash
The problem
After a dev server died ungracefully mid-build, the restart failed with:
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenListen (node:net:1917:16)
No terminal admitted to running anything, but the port was bound.
What didn't work
- Rebooting — works, takes five minutes, and you'll be doing it again tomorrow.
- Silently switching to
PORT=3001— masks the zombie, which is still holding the old socket and serving stale code to anyone who hits it. pkill node— nukes every Node process on the box, including the one that didn't deserve it.
The fix
# identify the holder by port, inspect, then kill precisely:
lsof -nP -iTCP:3000 -sTCP:LISTEN
# -> node 41321 you 23u IPv6 ... TCP *:3000 (LISTEN)
kill -9 $(lsof -ti tcp:3000)
// make the server fail loudly instead of hiding behind a mystery port
const PORT = process.env.PORT ?? 3000;
const server = app.listen(PORT);
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(
`port ${PORT} is held by another process — run: kill -9 $(lsof -ti tcp:${PORT})`
);
process.exit(1);
}
throw err;
});
Why it works
The old process survived its parent (SIGKILLed shell, detached debugger, crashed watcher) and still owns the listening socket; killing by port finds it regardless of its terminal or process name, and the error handler turns the next silent fallback into a message that names the fix.