Runtimes Intermediate

Resolving NPM EADDRINUSE: Port Already In Use by Node.js Processes on macOS

Encountering 'EADDRINUSE' with Node.js on macOS? This expert guide provides step-by-step troubleshooting to identify and terminate processes occupying desired network ports, ensuring your NPM scripts run smoothly.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Encountering 'EADDRINUSE' with Node.js on macOS? This expert guide provides step-by-step troubleshooting to identify and terminate processes occupying desired network ports, ensuring your NPM scripts run smoothly.

Introduction

As a seasoned Systems Administrator and DevOps Engineer, few errors are as common, yet as frustrating, in local Node.js development environments as EADDRINUSE. This error signals that the network address (specifically, a combination of IP address and port) your application is attempting to bind to is already in use by another process. On macOS, this frequently occurs when a Node.js server process (e.g., a development server, API backend, or front-end build tool) didn't shut down cleanly, or you've inadvertently launched multiple instances of the same application. This guide will provide a highly technical, accurate, and actionable approach to diagnose and resolve this issue, integrating best practices applicable to both local development and broader hosting environments.

Symptom & Error Signature

When you attempt to start your Node.js application, typically via an npm run command (e.g., npm start, npm dev), your terminal will display an error message similar to the following, indicating that the desired port is unavailable:

$ npm start

> [email protected] start /Users/sysadmin/my-app
> node server.js

(node:12345) UnhandledPromiseRejectionWarning: Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _setupListenHandle] (net.js:1303:16)
    at Server.listen (net.js:1391:8)
    at Function.listen (/Users/sysadmin/my-app/node_modules/express/lib/application.js:618:24)
    at Object.<anonymous> (/Users/sysadmin/my-app/server.js:20:5)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47
(node:12345) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process, use the "npm signal" command.
(Use `node --trace-warnings ...` to show where the warning was created)
events.js:304
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _setupListenHandle] (net.js:1303:16)
    at Server.listen (net.js:1391:8)
    at Function.listen (/Users/sysadmin/my-app/node_modules/express/lib/application.js:618:24)
    at Object.<anonymous> (/Users/sysadmin/my-app/server.js:20:5)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47
Emitted 'error' event on Server instance at:
    at emitErrorNT (net.js:1329:8)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  code: 'EADDRINUSE',
  errno: -48,
  syscall: 'listen',
  address: '::',
  port: 3000
}

The key indicator is Error: listen EADDRINUSE: address already in use :::3000 (or whatever port your application is configured to use). This explicitly states the problem.

Root Cause Analysis

The EADDRINUSE error, or "Address already in use," occurs when an application attempts to bind to a network socket (an IP address and port combination) that is already held by another active process. On macOS, the typical underlying reasons include:

  1. Orphaned Process: Your previous Node.js application instance (or another server application) crashed or was terminated improperly (e.g., closing the terminal window without gracefully stopping the server, a forced shutdown). The operating system still registers the port as being in use by this "zombie" process, even if it's no longer actively processing requests.
  2. Multiple Instances: You've accidentally launched the same Node.js application multiple times, each attempting to listen on the identical port. This is common when rapidly iterating during development and forgetting to terminate a previous instance before starting a new one.
  3. Conflicting Services: Another application or service running on your macOS system (e.g., a different development server, a Docker container mapping to a host port, a system service like Apache or Nginx that is misconfigured, or even a different Node.js project) is already listening on the target port.
  4. Slow Port Release: After a process terminates, operating systems often keep ports in a TIME_WAIT state for a short period to handle any lingering packets. While less common for Node.js EADDRINUSE in local dev, it can occasionally contribute, especially if an application immediately tries to restart on the exact same port after a hard kill.

Understanding that a process, identified by its Process ID (PID), is the sole entity that can "own" a port at any given time is crucial. Our goal is to locate that process and terminate it.

Step-by-Step Resolution

This section outlines a methodical approach to resolve the EADDRINUSE error on macOS, focusing on identifying and terminating the offending process.

1. Identify the Occupied Port

First, confirm the exact port number your Node.js application is trying to use. This is usually specified in your application's configuration (e.g., process.env.PORT or hardcoded in server.js, app.js, index.js). From the error signature above, the port is 3000.

2. Find the Process Listening on the Port

We'll use the lsof (list open files) command, a powerful utility available on most Unix-like systems, including macOS, to find which process is using the specified port.

Open your macOS Terminal and execute:

lsof -i :<PORT_NUMBER>

Replace <PORT_NUMBER> with the actual port your application failed to bind to (e.g., 3000).

Example:

lsof -i :3000

Expected Output:

COMMAND   PID        USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node      12345  sysadmin    5u  IPv6 0xdeadbeef12345678      0t0  TCP *:3000 (LISTEN)

In this output:

  • COMMAND: The name of the process (e.g., node).
  • PID: The Process ID (e.g., 12345). This is what we need.
  • USER: The user running the process.
  • NAME: The network address and port (*:3000 (LISTEN) indicates it's listening on port 3000 on all interfaces).

If lsof returns no output, it means no process is actively listening on that port according to lsof. In such rare cases, the port might be in a TIME_WAIT state, or a different network configuration issue is at play. Try waiting a minute and retrying, or rebooting your machine as a last resort. Also ensure your application isn't attempting to bind to a different IP address specifically.

3. Terminate the Occupying Process (Gracefully)

Once you have the PID from the lsof output, the next step is to terminate that process. Always try a graceful termination first.

Use the kill command with the identified PID:

kill <PID>

Example:

kill 12345

This sends a SIGTERM signal (signal 15) to the process, requesting it to shut down cleanly. Many applications, including Node.js, are designed to catch SIGTERM and perform cleanup tasks before exiting.

4. Terminate the Occupying Process (Forcefully, if needed)

If the process doesn't terminate after a few seconds, or if kill <PID> didn't resolve the issue (e.g., the process is unresponsive), you may need to force-kill it.

Using kill -9 (SIGKILL) is a forceful termination. The process will be immediately stopped without any chance to perform cleanup operations. This can lead to data corruption or inconsistent states for some applications, though it's generally safe for stateless Node.js dev servers. Use this as a last resort after attempting a graceful kill.

kill -9 <PID>

Example:

kill -9 12345

5. Verify the Port is Free

After terminating the process, it's good practice to verify that the port is now free. Rerun the lsof command:

lsof -i :3000

If no output is returned, the port is free, and you can now restart your Node.js application.

6. Restart Your Node.js Application

With the port clear, navigate back to your project directory and restart your application:

npm start

Or your specific npm run command. It should now launch successfully.

7. Best Practices for Prevention

To minimize future EADDRINUSE occurrences, especially in development and in robust hosting environments, consider these best practices:

For Local Development (macOS):
  • Use nodemon: nodemon is a utility that will monitor for any changes in your source and automatically restart your server. Crucially, it handles SIGTERM gracefully on restart, preventing orphaned processes.
    npm install -g nodemon
    # Then, instead of `node server.js`, use:
    nodemon server.js
    
  • Editor/IDE Integration: Ensure your IDE (VS Code, WebStorm) is configured to send proper termination signals when you stop a run task, rather than just detaching from the process.
  • kill-port Package: For complex development workflows or CI scripts, you might use an npm package like kill-port in your prestart or predev scripts to ensure the port is always clear.
    // package.json
    "scripts": {
      "predev": "kill-port 3000 || true",
      "dev": "nodemon server.js"
    }
    
    (Remember to install kill-port as a dev dependency: npm install --save-dev kill-port)
  • Dynamic Port Allocation: For applications that can tolerate it, configure them to automatically find an available port if the default is taken. Many frameworks (e.g., React's create-react-app) do this by default in development.
For Hosting/Production Environments (Ubuntu/Debian, Docker, Systemd):

While this guide focuses on macOS local development, EADDRINUSE principles extend to server environments. Robust process management is key here:

  • Process Managers (e.g., PM2, Systemd):
    • PM2 (Node.js Process Manager): For production Node.js applications, PM2 is invaluable. It keeps applications alive indefinitely, manages logs, and provides graceful restarts and shutdowns, effectively preventing orphaned processes.
      # Install PM2 globally
      npm install pm2 -g
      # Start your app with PM2
      pm2 start server.js --name "my-node-app"
      # Stop PM2 process
      pm2 stop my-node-app
      # Delete PM2 process
      pm2 delete my-node-app
      
    • Systemd (Ubuntu/Debian): For any service on Linux, systemd is the standard. A well-configured systemd service unit file ensures your application starts, stops, and restarts cleanly.
      # /etc/systemd/system/my-node-app.service
      [Unit]
      Description=My Node.js Application
      After=network.target
      
      [Service]
      ExecStart=/usr/bin/node /path/to/your/app/server.js
      WorkingDirectory=/path/to/your/app
      Restart=always
      User=www-data # Or your application user
      Environment=PORT=3000
      
      [Install]
      WantedBy=multi-user.target
      
      systemctl start my-node-app, systemctl stop my-node-app, systemctl restart my-node-app will manage the process lifecycle reliably.
  • Docker/Containerization:
    • Managed Port Mapping: When using Docker, EADDRINUSE on the host machine often means a container is trying to map to a host port that's already in use (either by another container or a host process). Ensure your docker-compose.yml or docker run commands use unique host ports or manage them carefully.
      # docker-compose.yml
      version: '3.8'
      services:
        webapp:
          build: .
          ports:
            - "3000:3000" # Host_Port:Container_Port
      
      If you run docker-compose up twice, you'll get EADDRINUSE. Always use docker-compose down to stop and remove services, freeing up ports.
  • Nginx/Reverse Proxies: If Nginx is serving as a reverse proxy, it listens on standard HTTP/HTTPS ports (80/443). Your Node.js application will listen on an internal port (e.g., 3000, 8080) that Nginx then proxies to. Ensure Nginx itself isn't attempting to proxy to a non-existent upstream or that your application isn't trying to bind to Nginx's ports.

By understanding the root causes and applying these systematic troubleshooting and prevention methods, you can efficiently resolve EADDRINUSE errors and maintain a robust development and deployment workflow.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn →

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.