Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Port already in use: find what is holding it and free it
Tools

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.

By Max Arthur
Co-Founder & Content Marketer·October 8, 2026·7 Min read
White text reading 'How to fix port already in use' on a dark purple dotted background
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.

Keep reading

More from the blog

White text reading 'Why React lists need unique keys' on a dark purple dotted background
October 16, 2026·7 min readTools

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 more
White text reading 'Too many re-renders in React' on a dark purple dotted background
October 15, 2026·9 min readTools

Too 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 more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons