Fix “EADDRINUSE: address already in use” on a local port
By Flavio
What the EADDRINUSE error means, how to see which process holds the port, and how to stop the right one safely with lsof and kill or Port Pilot.
You run npm run dev and the server dies before it starts:
Error: listen EADDRINUSE: address already in use :::3000
Another process is already listening on port 3000, so your server can’t take it. The fix is to find that process, decide whether you still need it, and then stop it or start your server on another port.
The quick manual version is lsof -i :3000 to get the process ID, then kill with that ID. I explain both commands, the -9 flag and the cases where lsof prints nothing in Find and kill the process using a port. Here we’ll focus on the part those commands don’t answer: what that process is, and whether it’s safe to stop. Port Pilot shows the project and command behind a port before it stops anything, so we’ll use its ports command for that.
What you need
Port Pilot’s command-line tool runs on macOS and Linux with Node.js 20 or newer. Docker is optional. After purchasing it, connect GitHub in Polar, accept the private repository invitation, and download the source from Releases. From the repository root, run npm install, npm run verify and npm link to install the ports command.
The download also includes a macOS menu bar app as SwiftUI source. You don’t need it for this tutorial. Port Pilot needs no account, and process details and health results aren’t sent anywhere.
Check the commands your installed version supports:
ports --help
This tutorial uses check, kill, is-free and list.
Read the error message
Every runtime words the error differently. Here’s what you’ll see from a few common tools:
| Tool | What it prints |
|---|---|
| Node.js | Error: listen EADDRINUSE: address already in use :::3000 |
| Python on macOS | OSError: [Errno 48] Address already in use |
| Python on Linux | OSError: [Errno 98] Address already in use |
| Vite | Port 5173 is in use, trying another one... |
Vite with --strictPort | Error: Port 5173 is already in use |
In Node’s message, :: means “every address”, and the number after the last colon is the port. Python’s error doesn’t include the port, so take it from the command you ran.
Vite doesn’t fail. It moves to the next free port, which is how you end up with the same app on 5173 and 5174 and a browser tab still pointing at the old copy.
Find out what holds the port
Ask Port Pilot about that one port:
ports check 3000
It inspects whatever is listening on 3000: the process, the project it belongs to, the command that started it, and whether it answers over HTTP. If Docker published the port, you also see the container name and image.
This is the information lsof leaves out. lsof tells you node and a PID. When you have five Node projects open, that doesn’t tell you which one it is.
If you’d rather look at everything at once, run ports with no arguments. It opens a live dashboard of your development services with their ports, projects, frameworks, memory, uptime and commands. Select a row to see the full command and the chain of processes that launched it.
The dashboard starts with development services. If the port you’re after isn’t there, list every listener, system processes included:
ports list --all
Decide whether to stop it
Before you stop anything, match what you found to one of these cases:
- It’s your app’s previous run, still going in another terminal tab. Go to that tab and press
Ctrl+C. - It’s another project you still want running. Leave it alone and start your app on a different port. Most dev servers accept a
--portflag or aPORTvariable, and I wrote how to change a Next.js app port. If juggling port numbers gets old, you can give each project a name instead, as in HTTPS on localhost with Caddy. - It’s a Docker container. Stop the container with
docker stopand its name, so Docker knows it’s stopped. - It’s a leftover with no terminal: a server you started in the background and forgot, or a process that outlived the script that started it. This is the one to stop.
Stop it and check the port is free
Ask Port Pilot to stop the process on the port:
ports kill 3000
Nothing stops yet. Port Pilot first shows the process, project, directory, command, PID and process chain, then asks you to confirm. Read it. If the project isn’t the one you expected, say no.
After you confirm, Port Pilot finds the processes started by the listening one and stops them first, then the listener. It sends a graceful signal and only forces termination when a process doesn’t exit after the grace period. That gives a dev server the chance to clean up, which kill -9 doesn’t.
In the dashboard, the same action is Stop safely in the key bar at the bottom, with the same preview and confirmation.
Now check the port:
ports is-free 3000 && echo free
is-free exits with status 0 when the port is free and 1 when it’s taken, so you see free only when you can use it. Start your server again with npm run dev. You should get your usual startup message instead of the error.
Let scripts and agents check before they start
A script or a coding agent can’t answer a confirmation prompt, and Port Pilot doesn’t treat that as a yes. Automation must pass --yes explicitly, and asking for JSON output doesn’t count as permission:
ports kill 3000 --yes
Only allow that for processes the agent started itself. For everything else, have it look and ask. Put this in your project’s agent instructions:
Before starting the dev server, run `ports is-free 3000`. If the port is
taken, run `ports check 3000 --json` and tell me what is listening there.
Don't stop a process you didn't start without asking me first.
The agent gets the same project and command details you saw, as JSON, without scraping a colored terminal table.
If something looks wrong
If ports check finds nothing but the error stays, the listener may belong to a system service or another user. Run ports list --all, which includes system and unknown listeners. With lsof, run sudo lsof -i :3000.
If port 5000 or 7000 is taken on a Mac and the process is ControlCenter, that’s AirPlay Receiver. Apple uses those ports for AirPlay. Turn off AirPlay Receiver in System Settings > General > AirDrop & Handoff, or run your app on another port.
If the port is taken again a few seconds after you stop it, something restarted the server: a file watcher, a process manager, or a container with a restart policy. The process chain in the preview shows what launched it. Stop that parent where it runs instead of stopping the child again.
If your shell says ports: command not found, run npm link again from the Port Pilot folder and check that npm’s global bin directory is on your PATH.
Get Port Pilot
Inspect local services, check their health, and stop the right process through a terminal dashboard, CLI, or macOS menu bar app.
See Port Pilot → · $20 including tax, with full source code.
Want me to talk about your product? You can sponsor this site.