Error fix · Developer errors

Node.js EADDRINUSE: address already in use :::3000 Fix

Fix Node.js "Error: listen EADDRINUSE: address already in use" by finding the process holding the port with lsof, ss or netstat, then stopping it or changing ports.

Error messageError: listen EADDRINUSE: address already in use :::3000

By SiteDownCheck Editorial · Updated October 14, 2026 · 5 min read

EADDRINUSE means your Node.js process asked the operating system to listen on an address and port that something else is already bound to. Node does not pick another port for you. It throws, and unless you handle the error the process exits.

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (node:net:1817:16)
    at listenInCluster (node:net:1865:12)
  code: 'EADDRINUSE',
  errno: -98,
  syscall: 'listen',
  address: '::',
  port: 3000

The :::3000 is the IPv6 wildcard address :: followed by port 3000. If you see 127.0.0.1:3000 or 0.0.0.0:3000 instead, the logic is identical. The errno is -98 on Linux, -48 on macOS and -4091 on Windows.

Decision order

  1. Find out what owns the port (below).
  2. If it is an old copy of your own app, stop it.
  3. If it is a different program, change your port or stop that program.
  4. If nothing owns it but the error persists, suspect a duplicate listen() call, a cluster or process-manager misconfiguration, or a container publishing the same port.

Step 1: find the process holding the port

Linux:

sudo ss -ltnp 'sport = :3000'
sudo lsof -iTCP:3000 -sTCP:LISTEN -n -P

macOS:

lsof -iTCP:3000 -sTCP:LISTEN -n -P

Windows (Command Prompt or PowerShell):

netstat -ano | findstr :3000
tasklist /FI "PID eq 12345"

Replace 12345 with the PID from the last column of the netstat output. The ss and lsof output shows the command name and PID directly. Without sudo, Linux hides processes owned by other users.

You can also check from the outside with the Port Checker if the port is meant to be public, though a local firewall can make a listening port look closed.

Step 2: stop the old process

If the PID is a stale Node process, ask it to exit cleanly first:

kill 12345        # SIGTERM, lets handlers run
kill -9 12345     # last resort, SIGKILL

On Windows: taskkill /PID 12345 /F. Do not kill a PID you have not identified. A database, a reverse proxy or another team member's service may legitimately use the port.

Common stale-process sources:

  • A terminal tab or IDE task still running npm start or nodemon.
  • A previous run that crashed in the parent but left a child process alive (a watcher, a ts-node child, a test runner).
  • A process manager such as pm2 restarting the app while you also run it by hand. Check with pm2 list.
  • A systemd unit already running the same service. Check with systemctl status yourapp.

Step 3: change the port

Read the port from the environment so you can move it without editing code:

const port = Number(process.env.PORT) || 3000;
const server = app.listen(port, () => console.log(`listening on ${port}`));

server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.error(`Port ${port} is already in use`);
    process.exit(1);
  }
  throw err;
});

Run with PORT=3001 node server.js. In tests, listen on port 0 and the OS assigns a free port; read it from server.address().port. That removes port collisions between parallel test files entirely.

Step 4: nothing is listening, but it still fails

  • Two listen() calls in one process. A module imported twice, or both an app.listen and a separate http.createServer(...).listen on the same port, collides with itself. Search your code for every listen(.
  • Cluster or worker mistakes. In Node's cluster module, workers share the primary's listening handle. Calling listen on a different mechanism in each worker, or starting the app once per CPU with a process manager and a fixed port, causes this. Use cluster mode properly or put a reverse proxy in front.
  • Docker. A container publishing -p 3000:3000 takes the host port. A container you forgot about counts. Run docker ps and see Docker: bind address already in use.
  • Host versus wildcard overlap. One program bound to 0.0.0.0:3000 blocks another from binding 127.0.0.1:3000 on the same port. Dual-stack behavior on :: differs by operating system, so a clash can appear on one machine and not another.
  • Windows reserved ranges. If the error is EACCES rather than EADDRINUSE on Windows, the port may sit inside a reserved range. List them with netsh interface ipv4 show excludedportrange protocol=tcp.
  • Ports under 1024 on Linux need root or a capability, and fail with EACCES, not EADDRINUSE. Prefer a reverse proxy on 80 and 443 with your app on a high port.

Production notes

Zero-downtime restarts with a fixed port work only if the old process releases the port before the new one binds, or you use a process manager or load balancer that overlaps them. If a deploy script starts the new version before stopping the old one, you get exactly this error. Add a health-check endpoint and watch it with an uptime monitor so a crash loop after deploy does not go unnoticed. A broader checklist is in Docker website down checklist if your app runs in containers.

Next steps

FAQ

Why does Node say :::3000 and not localhost?

:: is the IPv6 wildcard address. Node binds to it by default when you do not pass a host, and on most systems it also accepts IPv4 connections.

Can two Node processes share one port?

Not normally. Only one listener per address and port, unless you use the cluster module's shared handle or the platform's port reuse option.

The port is free but I still get EADDRINUSE. Why?

Look for a second listen() call in the same process, a hidden Docker container, or a different address family holding the port. Re-run the lsof or ss command with sudo.

Is it safe to kill -9 the process?

It works, but it skips cleanup such as closing database connections and flushing logs. Try plain kill first.

Get notified when this fix updates

Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.

This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.