Something already listens on that port — usually a previous instance of your own dev server that never died. Find it by port, decide whether to kill it, and add graceful shutdown for next time.
Ctrl-C during a hung build, nodemon forking, or a detached process: ps aux | grep node shows the straggler holding the port.
Port 3000 is famously fought over (React dev, Grafana, others). lsof -i :3000 -sTCP:LISTEN identifies the real owner before you kill it.
lsof -i :3000 -sTCP:LISTEN -P -n # or: ss -tlnp | grep :3000
kill <pid> # stubborn: kill -9 <pid> (see it: kill -l vs your app's signal handlers)
// app: const srv = app.listen(port); process.on('SIGTERM', () => srv.close(() => process.exit(0))); // + SIGINT
PORT=3001 npm start # package.json scripts: "dev": "PORT=3001 node server.js"
Dev tip: PORT=0 makes Node pick a free port and log the chosen one (server.address().port). In containers, EADDRINUSE at startup usually means your process forks/clusters with reusePort unset, or two containers map the same host port.
Either it wasn't the listener (check lsof output — it must show LISTEN state), or the port sits in TIME_WAIT. SO_REUSEADDR (Node sets it by default on listen) handles TIME_WAIT; a hard kill -9 skips your close handlers, so always prefer plain kill.
Configurable PORT per developer, graceful SIGTERM/SIGINT handlers, and a predev script that checks/frees the port. Docker dev setups isolate it per container entirely.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.