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.
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:
- 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.
- 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.
- 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.
- Slow Port Release: After a process terminates, operating systems often keep ports in a
TIME_WAITstate for a short period to handle any lingering packets. While less common for Node.jsEADDRINUSEin 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
lsofreturns no output, it means no process is actively listening on that port according tolsof. In such rare cases, the port might be in aTIME_WAITstate, 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 gracefulkill.
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:nodemonis a utility that will monitor for any changes in your source and automatically restart your server. Crucially, it handlesSIGTERMgracefully 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-portPackage: For complex development workflows or CI scripts, you might use annpmpackage likekill-portin yourprestartorpredevscripts to ensure the port is always clear.
(Remember to install// package.json "scripts": { "predev": "kill-port 3000 || true", "dev": "nodemon server.js" }kill-portas 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,
systemdis the standard. A well-configuredsystemdservice 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.targetsystemctl start my-node-app,systemctl stop my-node-app,systemctl restart my-node-appwill manage the process lifecycle reliably.
- 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.
- Docker/Containerization:
- Managed Port Mapping: When using Docker,
EADDRINUSEon 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 yourdocker-compose.ymlordocker runcommands use unique host ports or manage them carefully.
If you run# docker-compose.yml version: '3.8' services: webapp: build: . ports: - "3000:3000" # Host_Port:Container_Portdocker-compose uptwice, you'll getEADDRINUSE. Always usedocker-compose downto stop and remove services, freeing up ports.
- Managed Port Mapping: When using Docker,
- 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.
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.