Port already in use: find what is holding it and free it
Port already in use means another process is still listening. Here is how to find it on any platform, free the port, and stop it recurring.
Error: listen EADDRINUSE: address already in use :::3000
A port can only be held by one listening process at a time. This error means something is already listening on the one you asked for, and the operating system refused to give it to you twice.
There are only two outcomes: find and stop whatever is holding the port, or use a different port. Everything else is detail.
The output here came from Node v24.14.1.
Reading the message
The error object carries more than the message line:
code=EADDRINUSE syscall=listen address=:: port=3000
syscall=listen says the failure happened while binding, before your server ever handled a request. Nothing in your application code ran.
The odd part is :::3000, which looks like a typo and is not. It is two things joined: :: is the IPv6 address meaning "every interface", and :3000 is the port. So the message reads "every interface, port 3000". You will also see 0.0.0.0:3000, which is the same idea in IPv4.
That detail matters once, in a specific case. A process listening on :: usually occupies the IPv4 address too, which is why binding to localhost:3000 can fail when the other process looks like it is on a different address.
Find what is holding it
Each platform has its own command, and all of them give you a process ID.
macOS and Linux:
lsof -i :3000
Windows:
netstat -ano | findstr :3000
The Windows output has the PID in the last column, and lists one row per address family:
TCP 0.0.0.0:4123 0.0.0.0:0 LISTENING 9864
TCP [::]:4123 [::]:0 LISTENING 9864
Both rows show the same PID, 9864. That is one process listening on both stacks, not two processes competing.
Before killing anything, find out what it is:
ps -p 9864 -o command # macOS and Linux
tasklist /FI "PID eq 9864" # Windows
Nine times out of ten it is a server you started earlier and forgot. Occasionally it is something you should not stop, which is the reason to look rather than to kill blindly.
Free the port
Once you know the PID:
kill -9 9864 # macOS and Linux
taskkill /PID 9864 /F # Windows
The port is released immediately. Running the same bind again after the holder exits succeeds with no waiting and no cleanup step.
There is a blunter Windows command that circulates widely:
taskkill /F /IM node.exe
That kills every Node process on the machine, including editors, language servers and any other project you have running. It works, and it is worth understanding that it does much more than free one port.
If you would rather not identify the process at all, npx kill-port 3000 does the lookup and the kill in one step on all three platforms.
Or just use a different port
Often the faster answer. Nothing is wrong with port 3001:
PORT=3001 npm run dev
That only works if your code reads the variable, which is worth setting up once:
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`listening on ${port}`));
Reading the port from the environment is also what deployment platforms expect, since most of them assign a port and tell you about it through PORT. A hard-coded 3000 fails in exactly that situation.
Why it keeps happening
If this error appears several times a day, the cause is usually one of these.
The previous process never exited. Closing a terminal tab does not always stop what it was running, and a process started in the background outlives the window entirely. It keeps the port until it is killed.
A crash left it running. A supervisor such as nodemon restarts your code after a change, but if the old process is wedged rather than stopped, the restart tries to bind a port the old one still holds. The symptom is the error appearing on save rather than on start.
Two things want the same port. Two projects both defaulting to 3000, or a dev server and a test runner that spins up its own. Giving each project a distinct port in its own configuration removes the collision permanently.
The process is suspended rather than stopped. On macOS and Linux, Ctrl+Z suspends a job rather than ending it. A suspended process is still alive, and a live process keeps its port, which was confirmed here directly: binding failed while the holder was running and succeeded the moment it was killed. Ctrl+C stops it properly. If you have suspended jobs, jobs lists them and kill %1 clears one.
Errors that look like this one
Three other bind failures get mistaken for a busy port, and each needs a different fix:
| Code | Cause | Fix |
|---|---|---|
EADDRINUSE |
Another process holds the port | Kill it, or change port |
EACCES |
Port below 1024 without permission | Use a high port, or a reverse proxy |
EADDRNOTAVAIL |
The address is not on this machine | Bind to localhost or 0.0.0.0 |
ERR_SOCKET_BAD_PORT |
Port outside 0 to 65535 | Fix the number |
EADDRNOTAVAIL is the one worth recognising, because it reads like a port problem and is not:
listen(3000, "192.0.2.55") => EADDRNOTAVAIL
The port is free. That IP address simply does not belong to this machine, so there is no interface to listen on. It usually means a hard-coded address from another environment.
EACCES is platform dependent, and the difference surprises people who move between systems. On macOS and Linux, ports below 1024 are privileged and binding to port 80 as a normal user is refused. On Windows there is no such restriction, and binding to 80 and 443 succeeded here without any elevation. Code that runs on your Windows machine and fails on a Linux server with EACCES has met exactly this difference.
ERR_SOCKET_BAD_PORT behaves differently from the others in a way that matters. It is thrown immediately rather than delivered to the error handler, so a server.on("error", ...) block never sees it and the process crashes with a RangeError. Validate a port that came from configuration before you pass it in.
Let the system pick, in tests
There is a fourth option nobody mentions, and it is the right one for test suites:
server.listen(0, () => console.log(server.address().port));
// bound OK on 11610
Port 0 means "any free port". The operating system picks one, and server.address().port tells you which. A test that binds port 0 can never collide with your dev server, another test, or a colleague's machine, which removes this entire class of flaky failure from CI.
Handle it in code
A server that dies on a bind failure with a stack trace is unhelpful. Catching the error lets you say something useful:
server.on("error", (e) => {
if (e.code === "EADDRINUSE") {
console.error(`Port ${port} is in use. Try PORT=${port + 1} npm run dev`);
process.exit(1);
}
throw e;
});
Checking e.code rather than matching on the message text is the part that matters. Message wording changes between versions and platforms; the code does not.
The other half is shutting down cleanly, so the port is released when you stop the process rather than left for the operating system to reclaim:
process.on("SIGINT", () => server.close(() => process.exit(0)));
server.close() stops accepting new connections and frees the port. Without it, an abrupt exit can leave connections in a state that delays reuse, which is the other reason a port sometimes seems busy after the process has gone.
The habit that prevents most of it
Stop servers with Ctrl+C in the terminal that started them, and check that the process actually exited before starting another. When you run something in the background on purpose, write down the PID or use a process manager that can list and stop what it started.
Most of the time this error is not a bug at all. It is the operating system correctly reporting that you already have the thing you are trying to start.
Want to run servers somewhere a stray process cannot bite you? Start with the JavaScript track and build up to the Node work in the browser.
More from the blog
Each child in a list should have a unique key prop
The unique key prop warning in React still renders your list. Here is the bug it predicts, shown with real output from two runs.
Read moreToo many re-renders in React: the one real cause
Too many re-renders always means a state update running during render. Here is how to spot which of the four shapes you have written.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.