Fix “Error: listen EADDRINUSE: address already in use” in Node.js
On this page
- What “EADDRINUSE: address already in use” means
- The usual causes
- Fix 1: Find the process using the port
- macOS and Linux
- Windows
- Fix 2: Stop that process
- Fix 3: Use a different port
- Fix 4: Handle the error instead of crashing
- Fix 5: Shut down cleanly so the port is always released
- When tests cause EADDRINUSE
- Nodemon, Docker and other repeat offenders
- A 60-second diagnosis checklist
- Habits that prevent the error
- Frequently asked questions about “EADDRINUSE: address already in use”
- Oh hi there 👋It’s nice to meet you.
- Sign up to receive awesome content in your inbox.
You run npm run dev and instead of “listening on port 3000” your terminal shows Error: listen EADDRINUSE: address already in use :::3000. The message means exactly what it says: another process is already listening on that port, and the operating system allows only one listener per port. Most of the time, that other process is an older copy of your own server that never stopped. This guide shows how to find it, stop it, and make sure it doesn’t happen again.
What “EADDRINUSE: address already in use” means
A port is like a numbered door on your computer. When your Express or Node.js server calls listen(3000), it asks the operating system for door 3000. If another program already holds it, the request is refused with the error code EADDRINUSE, short for “address in use”.
Here is the exact output from Node.js 22 when a second server starts on a port that is already taken:
node:events:497
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:1941:16)
at listenInCluster (node:net:1998:12)
at Server.listen (node:net:2103:7)
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1977:8)
The address part varies: :::3000 means “port 3000 on all IPv6 and IPv4 addresses”, while 0.0.0.0:3000 or 127.0.0.1:3000 name a specific address. The fix is the same either way.
The usual causes
| Cause | How it happens |
|---|---|
| An old copy of your server | A terminal you forgot about, a crashed watcher that left the process running, or closing the terminal window without stopping the server |
| Two terminals running the same app | Starting npm run dev again in a new tab |
Calling listen() twice |
For example, tests that import a file which starts the server, while the dev server is also running |
| A different program using the port | Another project, a Docker container, or a system service |
| macOS and port 5000 | The AirPlay Receiver feature in recent macOS versions listens on port 5000, a popular default for Express APIs |
Fix 1: Find the process using the port
First find out what holds the port. Replace 3000 with your port number.
macOS and Linux
lsof -i :3000 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 48213 dev 23u IPv6 ... 0t0 TCP *:3000 (LISTEN)
The PID column is the process ID. On Linux without lsof, ss -ltnp | grep :3000 shows the same information.
Windows
netstat -ano | findstr :3000
TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 48213
The last column is the process ID. To see which program it is, run tasklist /FI "PID eq 48213".
Fix 2: Stop that process
If the process is an old copy of your server, stop it:
# macOS and Linux
kill 48213 # polite request to stop
kill -9 48213 # force, only if it refuses
# Windows (Command Prompt or PowerShell)
taskkill /PID 48213 /F
There is also a cross-platform shortcut that finds and stops whatever holds a port:
npx kill-port 3000
Be careful with the “kill whatever holds the port” approach when the process isn’t yours: check the command name first, so you don’t stop a database, Docker or another app you need. In testing, once the old process was stopped, the same server started immediately on the freed port.
On macOS, if port 5000 is taken by ControlCe (Control Center), it is AirPlay Receiver. Either turn it off in System Settings under AirDrop and Handoff, or simply run your API on another port such as 5001.
Fix 3: Use a different port
Hard-coding a port makes conflicts more likely. Read it from an environment variable with a default:
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
# run on another port without changing code
PORT=3001 npm run dev # macOS and Linux
$env:PORT=3001; npm run dev # Windows PowerShell
Hosting platforms such as Render, Railway and Heroku set PORT for you, so reading it from the environment is required for deployment anyway. If your React app calls the API through a Vite proxy, update the proxy target to the new port too; see Vite + Express: set up a full-stack MERN project.
Fix 4: Handle the error instead of crashing
The stack trace above starts with Unhandled ‘error’ event: Node.js crashes because nothing listened for the server’s error. You can catch it and show a clear message, or automatically try the next port during development:
import http from 'node:http';
import app from './app.js';
function start(port, attemptsLeft = 5) {
const server = http.createServer(app);
server.once('error', (err) => {
if (err.code === 'EADDRINUSE' && attemptsLeft > 0) {
console.warn(`Port ${port} is in use, trying ${port + 1}...`);
start(port + 1, attemptsLeft - 1);
} else {
console.error(err);
process.exit(1);
}
});
server.listen(port, () => console.log(`Server running on http://localhost:${port}`));
}
start(Number(process.env.PORT) || 3000);
Tested with port 3456 already taken, this printed “Port 3456 is in use, trying 3457…” and then “Server running on http://localhost:3457”. Use this only in development: in production, a server silently moving to another port breaks your proxy and load balancer configuration, so it is better to fail loudly with a clear error.
Fix 5: Shut down cleanly so the port is always released
The best fix is making sure your server releases the port every time it stops. Listen for the shutdown signals sent by Ctrl+C, PM2, Docker and hosting platforms, and close the server before exiting:
const server = app.listen(PORT, () => console.log(`listening on ${PORT}`));
for (const signal of ['SIGINT', 'SIGTERM']) {
process.on(signal, () => {
console.log(`${signal} received, closing server`);
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10_000).unref(); // don't hang forever on open connections
});
}
In testing, pressing Ctrl+C (SIGINT) closed the server and the port was immediately free for a new process. If you also hold a database connection, close it in the same handler, for example with await mongoose.connection.close(). Graceful shutdown is covered in more depth in the Node.js production checklist.
When tests cause EADDRINUSE
A very common hidden cause: your test files import the module that calls app.listen(), so every test file starts a real server on the same port, or the tests collide with your running dev server. Split the app from the server:
// app.js: builds the Express app, does not listen
import express from 'express';
const app = express();
app.get('/health', (req, res) => res.json({ ok: true }));
export default app;
// server.js: the only file that listens
import app from './app.js';
app.listen(process.env.PORT || 3000);
// app.test.js: Supertest uses the app without a fixed port
import request from 'supertest';
import app from './app.js';
test('health', async () => {
await request(app).get('/health').expect(200);
});
Supertest starts the app on a temporary free port for each request, so tests never fight over port 3000.
Nodemon, Docker and other repeat offenders
- nodemon or
node --watch: if a restart happens while the old process is still shutting down, the new one can briefly hit EADDRINUSE. Graceful shutdown (Fix 5) usually solves this; a short restart delay helps too. - Docker:
docker psshows containers publishing ports, for example0.0.0.0:3000->3000/tcp. Stop the container or map it to another host port with-p 3001:3000. - VS Code debugger: a debug session left running keeps its server alive. Stop it from the debug toolbar.
- PM2:
pm2 listshows apps it keeps running in the background, even after you close the terminal. Usepm2 stop app-name.
A 60-second diagnosis checklist
When the error appears, work through these in order. Most cases are solved by the first two:
- Look for another terminal running the same app, including terminals inside your editor.
- Find the process on the port with
lsof -i :3000ornetstat -ano | findstr :3000, and check its name. - If it is Node.js and yours, stop it with
killortaskkill. - If it is something else, such as Docker, a database or AirPlay, leave it running and change your app’s port instead.
- If it keeps coming back, check process managers (
pm2 list,docker ps) and add graceful shutdown to your server. - If it only happens in tests, split
app.jsfromserver.jsso tests never calllisten().
Habits that prevent the error
- Give each project its own port and keep it in the project’s
.envfile, for example 3001 for one API and 3002 for another, so projects you run side by side never collide. - Stop servers with Ctrl+C instead of closing the terminal window, so your shutdown handler runs.
- Print the port on startup, as in the examples above, so you always know which copy of the app is answering.
- Add a health check route such as
/health. Opening it in the browser tells you instantly whether an old server is still running on that port.
Frequently asked questions about “EADDRINUSE: address already in use”
Why does EADDRINUSE happen even though I closed the terminal?
Closing a terminal window doesn’t always stop processes started from it, especially on Windows or with process managers. Find the process with lsof or netstat and stop it, or restart your computer as a last resort.
Is it safe to use kill -9?
It stops a process immediately without letting it clean up, so use it only when a normal kill doesn’t work. For your own dev server it is harmless; for databases, prefer a normal stop to avoid data loss.
What does ::: mean in the error message?
:::3000 means port 3000 on all network addresses, IPv6 and IPv4. It is the default when you call listen() without a host.
Can two Node.js servers share one port?
Not as separate programs. Node’s cluster module and PM2’s cluster mode let several worker processes of the same app share a port by design, but two unrelated servers can’t.
Why does my Express app use port 5000 on macOS and fail?
Recent macOS versions use port 5000 for AirPlay Receiver. Turn the feature off in System Settings, or run your API on a different port such as 5001.




