This error occurs when multiple cluster workers attempt to bind to the same port incorrectly, or when a worker tries to reopen a listener on a port it previously closed, causing EADDRINUSE conflicts in the cluster environment.
This error indicates a port binding conflict in Node.js cluster mode. While the cluster module is designed to allow multiple workers to share the same port through the primary process, this error occurs when workers attempt to manage port bindings incorrectly. In a properly configured cluster, when a worker calls server.listen(), the request is serialized and passed to the primary (master) process. The primary either returns an existing listening server handle or creates a new one. However, issues arise when workers try to close and reopen listeners on the same port, or when the port binding state becomes inconsistent between the primary and worker processes. A common scenario is when a worker closes a server and immediately tries to listen on the same port again. The primary process may return a cached file descriptor even if that descriptor has already produced an error, leading to EADDRINUSE errors or unpredictable behavior where the server binds to a random port instead of the intended one.
Ensure workers are using the cluster module's built-in port sharing mechanism. Workers should all listen on the same port, with the primary process handling distribution:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
});
} else {
// Workers share the same port
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}All workers should call listen() on the same port - the primary process handles sharing.
Always add error event handlers to your server instances to properly catch and handle port-related errors:
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`Port ${PORT} is already in use`);
process.exit(1);
} else {
console.error('Server error:', err);
}
});
server.listen(8000, () => {
console.log(`Worker ${process.pid} listening on port 8000`);
});This ensures errors are caught and logged instead of causing silent failures.
If you need to restart a server, restart the entire worker process instead of closing and reopening the listener:
// In primary process
cluster.on('message', (worker, message) => {
if (message.cmd === 'restart') {
console.log(`Restarting worker ${worker.id}`);
worker.kill();
cluster.fork();
}
});
// In worker process - request restart instead of close/reopen
if (needsRestart) {
process.send({ cmd: 'restart' });
}This avoids the close/reopen race condition that causes cached file descriptor issues.
If you don't need port sharing, assign unique ports to each worker based on worker ID:
if (cluster.isWorker) {
const basePort = 8000;
const workerPort = basePort + cluster.worker.id;
http.createServer((req, res) => {
res.writeHead(200);
res.end(`Worker ${cluster.worker.id} on port ${workerPort}\n`);
}).listen(workerPort);
console.log(`Worker ${process.pid} listening on port ${workerPort}`);
}This eliminates port sharing complexity entirely if it's not required for your use case.
When shutting down workers, ensure proper cleanup sequencing to avoid port conflicts:
// In worker process
let server;
function startServer() {
server = http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
});
server.listen(8000);
}
function gracefulShutdown() {
console.log(`Worker ${process.pid} shutting down`);
if (server) {
server.close(() => {
console.log('Server closed');
process.exit(0);
});
// Force shutdown after timeout
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 10000);
} else {
process.exit(0);
}
}
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
startServer();Wait for server.close() to complete before restarting to ensure the port is fully released.
Understanding Cluster Port Sharing
Node.js cluster uses two approaches for port sharing. On most platforms, it uses round-robin scheduling where the primary process listens on the port and distributes connections to workers. On Windows, it uses a shared socket handle approach. Understanding your platform's behavior helps debug port conflicts.
File Descriptor Caching
The primary process caches file descriptors for shared ports. When a worker calls listen(), the primary may return this cached descriptor even if it's stale or has errors. This is why close/reopen cycles fail - the cached descriptor represents the old, closed socket. The worker receives this stale descriptor and attempts to use it, leading to EADDRINUSE or random port binding.
Libuv Delayed Error Reporting
Port binding errors may not be reported immediately due to libuv's asynchronous nature. A worker might believe it successfully bound to a port when the actual bind() operation hasn't completed or has failed. This timing issue is exacerbated in cluster mode where the primary process intermediates all port operations.
UDP vs TCP
The cluster module only supports TCP port sharing, not UDP. Attempting to share UDP ports across workers will fail. Each UDP server needs its own unique port or you must implement a custom load balancing solution at the application level.
Worker Restart Strategies
For production systems using process managers like PM2 or cluster managers, configure zero-downtime restarts by starting new workers before killing old ones. This prevents port binding issues during deployments and ensures at least one worker is always available to handle the shared port.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams