Runtimes Intermediate

Troubleshooting NPM EADDRINUSE: Address Already in Use on Ubuntu 20.04 LTS Node.js Applications

Resolve 'EADDRINUSE address already in use' errors when starting Node.js applications on Ubuntu 20.04 LTS. Learn to identify and terminate conflicting processes quickly.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve 'EADDRINUSE address already in use' errors when starting Node.js applications on Ubuntu 20.04 LTS. Learn to identify and terminate conflicting processes quickly.

When deploying or managing Node.js applications on an Ubuntu 20.04 LTS server, encountering the EADDRINUSE error can be a common frustration. This error typically manifests when you attempt to start your Node.js application, and it fails to bind to its designated network port. Your application will not become accessible, and the startup command will terminate with a clear error message in the console or logs.

Symptom & Error Signature

You will typically observe this error in your terminal or application logs when attempting to start a Node.js application, often via npm start, node server.js, or a process manager like PM2.

Here's a common example of the error signature:

> [email protected] start
> node server.js

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (net.js:1316:16)
    at listenInCluster (net.js:1364:12)
    at Server.listen (net.js:1450:7)
    at Object.<anonymous> (/var/www/my-node-app/server.js:25:5)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'EADDRINUSE',
  errno: -98,
  syscall: 'listen',
  address: '::',
  port: 3000
}

The key information here is EADDRINUSE: address already in use :::3000, which explicitly states that port 3000 (or whatever port your application is trying to use) is already occupied.

Root Cause Analysis

The EADDRINUSE error occurs when a program attempts to bind to a network address and port that is already in use by another process. For Node.js applications on Ubuntu 20.04 LTS, the common underlying reasons include:

  1. Stale Process: A previous instance of your Node.js application (or another application) crashed, was not properly shut down, or is still running in the background, holding onto the port.
  2. Duplicate Application Instances: You or another administrator accidentally started the same Node.js application multiple times, each trying to listen on the same port. This is common in development environments where a process might be left running in a detached terminal.
  3. Conflicting Application: Another service or application (e.g., another web server, a database, a proxy like Nginx configured incorrectly, or a different Node.js app) is intentionally configured to listen on the same port.
  4. Improper Process Management: Without a robust process manager like PM2 or Systemd, Node.js applications can sometimes fail to release ports cleanly upon termination, especially if killed abruptly (e.g., via kill -9).

Step-by-Step Resolution

Follow these steps to diagnose and resolve the EADDRINUSE error.

1. Identify the Occupied Port

First, confirm which port your Node.js application is trying to use. This is usually specified in the error message itself (e.g., :::3000). If not, check your application's source code (e.g., server.js, app.js) where the listen() method is called.

For this guide, let's assume the port in question is 3000.

2. Find the Process Using the Port

You need to identify which process (and its Process ID, or PID) is currently listening on the problematic port. We'll use lsof or netstat/ss.

  • Using lsof (LiSt Open Files): lsof is often the most straightforward tool. If not installed, install it:

    sudo apt update
    sudo apt install lsof -y
    

    Then, run:

    sudo lsof -i :3000
    

    Replace 3000 with your actual port.

    Example output:

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

    In this example, node is the command, and 12345 is the PID.

  • Using netstat (Network Statistics): netstat is a classic tool, though ss is its modern replacement.

    sudo netstat -tulnp | grep :3000
    

    Example output:

    tcp6       0      0 :::3000                 :::*                    LISTEN      12345/node
    

    Again, 12345 is the PID and node is the process name.

  • Using ss (Socket Statistics): ss is faster and more efficient for large systems.

    sudo ss -tulnp | grep :3000
    

    Example output:

    tcp   LISTEN 0      128    [::]:3000              [::]:*                users:(("node",pid=12345,fd=18))
    

    This output also clearly shows pid=12345 and node.

If multiple processes are listed, carefully check the COMMAND or users field to identify the correct application. It's usually the one associated with node or your application's name.

3. Terminate the Conflicting Process

Once you have identified the PID (e.g., 12345), you can terminate the process.

sudo kill 12345

If the process doesn't terminate immediately, or if it's a stubborn "zombie" process, you might need to use a more forceful kill:

sudo kill -9 12345

Using kill -9 (SIGKILL) should be a last resort. It immediately terminates the process without allowing it to perform any cleanup or graceful shutdown, which can lead to data corruption or other issues if the application is handling critical operations. Always try sudo kill <PID> first.

After killing the process, you can verify that the port is free:

sudo lsof -i :3000
# Or
sudo ss -tulnp | grep :3000

There should be no output for the port if the process was successfully terminated.

4. Restart Your Node.js Application

Now that the port is free, you can restart your Node.js application.

If you typically start it manually:

cd /path/to/your/node/app
npm start
# Or
node server.js

If you're using a process manager like PM2:

pm2 restart my-node-app-name
# Or, if starting for the first time
pm2 start server.js --name my-node-app-name

5. Implement Robust Process Management with Systemd (Recommended for Production)

To prevent EADDRINUSE errors caused by stale processes and ensure your Node.js application starts reliably on server boot, it's highly recommended to use systemd.

  1. Create a Systemd Service File: Create a new service file for your application. Replace myapp with your application's name, and adjust paths/ports as necessary.

    sudo nano /etc/systemd/system/myapp.service
    

    Paste the following content, adjusting User, WorkingDirectory, and ExecStart:

    [Unit]
    Description=My Node.js Application
    After=network.target
    
    [Service]
    User=www-data # Or your application user (e.g., ubuntu, deploy)
    Group=www-data # Or your application group
    WorkingDirectory=/var/www/my-node-app # Path to your application's root directory
    ExecStart=/usr/bin/npm start # Or /usr/bin/node /var/www/my-node-app/server.js
    Restart=always
    RestartSec=3
    StandardOutput=syslog
    StandardError=syslog
    SyslogIdentifier=myapp-node
    Environment=NODE_ENV=production PORT=3000 # Define environment variables
    
    [Install]
    WantedBy=multi-user.target
    

    If your npm start script requires PATH to find node, you might need to specify the full path to node in ExecStart (e.g., /usr/bin/node server.js or /home/user/.nvm/versions/node/v18.x.x/bin/node server.js). You can find node's path with which node.

  2. Reload Systemd, Enable, and Start Your Service:

    sudo systemctl daemon-reload           # Reload systemd to recognize new service
    sudo systemctl enable myapp            # Enable service to start on boot
    sudo systemctl start myapp             # Start the service
    
  3. Check Service Status:

    sudo systemctl status myapp
    

    You should see active (running) if everything is configured correctly.

6. Verify Nginx Configuration (if applicable)

If you're using Nginx as a reverse proxy for your Node.js application, ensure Nginx is proxying to the correct port and is not attempting to bind to the Node.js application's internal port itself. Nginx typically listens on ports 80/443, while Node.js listens on a higher port (e.g., 3000, 8080).

Example Nginx configuration snippet for proxying to Node.js on port 3000:

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://localhost:3000; # Node.js app is listening here
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

The proxy_pass directive points to your Node.js application's internal listener. Nginx itself should not be configured to listen on port 3000.

7. Docker Considerations

If your Node.js application is running in a Docker container, the EADDRINUSE error typically means:

  • Inside the Container: Another process within that specific container is already using the port your Node.js app tries to bind to. Check the container's logs.
  • Host Port Mapping Conflict: You're trying to map a host port to the container, and that host port is already in use by a process on the Docker host machine.
    • Example: docker run -p 3000:3000 my-node-app. If host port 3000 is busy, this will fail.
    • To resolve: Find and kill the host process (steps 2 & 3 above) or choose a different host port (e.g., docker run -p 3001:3000 my-node-app).

By systematically following these steps, you can reliably resolve EADDRINUSE errors and ensure your Node.js applications run smoothly on Ubuntu 20.04 LTS.

👨‍💻

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.