Introduction

Node.js inspector debug port conflict (EADDRINUSE: address already in use :::9229) occurs when multiple Node.js processes attempt to bind to the same debug port. This is common in PM2 cluster mode, test runners that fork workers, Docker Compose with multiple Node.js services, or when a previous Node.js process did not shut down cleanly and still holds the port. The error prevents the inspector from starting, making remote debugging impossible.

Symptoms

``` node:net:1463 throw err; ^

Error: listen EADDRINUSE: address already in use :::9229 at Server.setupListenHandle [as _listen2] (node:net:1463:16) at listenInCluster (node:net:1511:12) at Server.listen (node:net:1599:7) at node:internal/main/inspect:42:12 ```

Or in PM2:

bash
PM2 | App [myapp:0] starting in -inspect mode-
PM2 | App [myapp:0] online
PM2 | App [myapp:1] errored - Port 9229 is already in use

Common Causes

  • Multiple processes on same port: PM2 cluster mode forks workers that all try to use 9229
  • Previous process not killed: Old Node.js process still running in background
  • Docker port conflict: Multiple containers mapping to host port 9229
  • Test runner with workers: Jest/Vitest forks workers that inherit the inspect flag
  • Port 9229 used by another application: Chrome DevTools, VS Code, or another Node.js instance
  • --inspect-brk in production: Debug flag accidentally left in production start script

Step-by-Step Fix

Step 1: Use automatic port assignment

```bash # Use port 0 for automatic assignment node --inspect=0 server.js # Output: Debugger listening on ws://127.0.0.1:34567/uuid # Node.js picks an available port automatically

# For PM2, use --inspect with different ports pm2 start server.js --name myapp -i 4 --node-args="--inspect=0"

# Each worker gets a unique port pm2 list # Check individual process logs for the assigned port ```

Step 2: Kill the process holding the port

```bash # Find what is using port 9229 lsof -i :9229 # OR on Linux ss -tlnp | grep 9229

# Kill the process kill $(lsof -t -i:9229)

# Or force kill if it does not respond kill -9 $(lsof -t -i:9229)

# Restart your application node --inspect server.js ```

Step 3: Configure PM2 with unique inspect ports

javascript
// ecosystem.config.js
module.exports = {
    apps: [{
        name: 'myapp',
        script: 'server.js',
        instances: 4,
        exec_mode: 'cluster',
        node_args: (ctx) => {
            // Assign unique inspect port per worker
            const basePort = 9229;
            return `--inspect=${basePort + ctx.node_app_inst_num}`;
        },
    }]
};

Or via command line:

```bash pm2 start server.js -i 4 \ --node-args="--inspect=9230" \ --exp-backoff-restart-delay=100

# Workers will use 9230, 9231, 9232, 9233 ```

Step 4: Disable inspector in production

json
{
    "scripts": {
        "start": "node server.js",
        "start:debug": "node --inspect server.js",
        "start:debug-brk": "node --inspect-brk server.js"
    }
}
bash
# Only use debug mode when needed
npm start              # Production - no inspector
npm run start:debug    # Development - inspector enabled

Prevention

  • Never use --inspect or --inspect-brk in production start scripts
  • Use --inspect=0 for automatic port assignment in multi-process environments
  • Add a startup check that verifies the debug port is not already in use
  • Configure PM2 ecosystem files with unique inspect ports per worker
  • Use lsof -i :9229 as part of your deployment health checks
  • Add .env variable NODE_OPTIONS=--inspect only in development environments
  • Monitor for EADDRINUSE errors in production logs as a sign of zombie processes