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 :::3000Error: listen EADDRINUSE 0.0.0.0:8080- Server crashes immediately on startup
- Port was free before, now occupied after a crash
lsof -i :3000shows 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.Find what is using the port:
- 2.```bash
- 3.# Linux/macOS
- 4.lsof -i :3000
- 5.# Output:
- 6.# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
- 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.Kill the process using the port:
- 2.```bash
- 3.# Kill specific process
- 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.Handle port release delay (TIME_WAIT):
- 2.```javascript
- 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.Use dynamic port assignment:
- 2.```javascript
- 3.// Find an available port
- 4.function findAvailablePort(startPort = 3000) {
- 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.Handle graceful shutdown to prevent stale ports:
- 2.```javascript
- 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=0to let the OS assign an available port