Introduction

When a Node.js server tries to listen on a port that is already in use, the operating system returns EADDRINUSE. This happens when a previous instance did not shut down cleanly, another service is using the same port, or the OS has not released the port from a recently closed socket (TIME_WAIT state).

Symptoms

  • Error: listen EADDRINUSE: address already in use :::3000
  • Error: listen EADDRINUSE 0.0.0.0:8080
  • Server crashes immediately on startup
  • Port was free before, now occupied after a crash
  • lsof -i :3000 shows another process

``` node:events:496 throw er; // Unhandled 'error' event ^

Error: listen EADDRINUSE: address already in use :::3000 at Server.setupListenHandle [as _listen2] (node:net:1811:16) at listenInCluster (node:net:1859:12) at Server.listen (node:net:1947:7) at Object.<anonymous> (/app/server.js:45:8)

Emitted 'error' event on Server instance at: ```

Common Causes

  • Previous server instance still running (crashed without cleanup)
  • Hot reload tool (nodemon, ts-node-dev) did not kill old process
  • Different service using the same port
  • Port in TIME_WAIT state after recent shutdown
  • Docker port mapping conflict

Step-by-Step Fix

  1. 1.Find what is using the port:
  2. 2.```bash
  3. 3.# Linux/macOS
  4. 4.lsof -i :3000
  5. 5.# Output:
  6. 6.# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
  7. 7.# node 12345 user 22u IPv6 12345 0t0 TCP *:3000 (LISTEN)

# Alternative netstat -tlnp | grep 3000 ss -tlnp | grep 3000

# Windows netstat -ano | findstr :3000 tasklist | findstr <PID> ```

  1. 1.Kill the process using the port:
  2. 2.```bash
  3. 3.# Kill specific process
  4. 4.kill 12345

# Force kill if normal kill does not work kill -9 12345

# One-liner: find and kill lsof -ti :3000 | xargs kill -9

# On Windows taskkill /PID 12345 /F ```

  1. 1.Handle port release delay (TIME_WAIT):
  2. 2.```javascript
  3. 3.const http = require('http');

const server = http.createServer(app);

// Allow immediate reuse of the address server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.log(Port 3000 is in use, waiting for release...); // Wait 1 second and retry setTimeout(() => { server.listen(3000); }, 1000); } else { console.error(err); process.exit(1); } });

server.listen(3000); ```

  1. 1.Use dynamic port assignment:
  2. 2.```javascript
  3. 3.// Find an available port
  4. 4.function findAvailablePort(startPort = 3000) {
  5. 5.const net = require('net');

return new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); server.on('error', () => { // Port is in use, try next resolve(findAvailablePort(startPort + 1)); }); server.listen(startPort, () => { server.close(() => { resolve(startPort); }); }); }); }

// Usage const port = await findAvailablePort(3000); app.listen(port, () => { console.log(Server running on port ${port}); }); ```

  1. 1.Handle graceful shutdown to prevent stale ports:
  2. 2.```javascript
  3. 3.const server = app.listen(process.env.PORT || 3000);

// Graceful shutdown function gracefulShutdown(signal) { console.log(Received ${signal}. Shutting down gracefully...); server.close(() => { console.log('Server closed. Process exiting.'); process.exit(0); });

// Force close after timeout setTimeout(() => { console.error('Forced shutdown due to timeout'); process.exit(1); }, 10000); }

process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); ```

Prevention

  • Always implement graceful shutdown handlers
  • Use server.close() before exiting to release ports cleanly
  • Use different ports for different environments (dev: 3000, test: 3001)
  • In Docker, use dynamic host port mapping: docker run -P
  • Add pre-start check in package.json scripts:
  • ```json
  • "scripts": {
  • "prestart": "lsof -ti :3000 | xargs kill -9 2>/dev/null || true",
  • "start": "node server.js"
  • }
  • `
  • Use process managers like PM2 that handle port conflicts automatically
  • In development, use PORT=0 to let the OS assign an available port