Find what is using a port and free it
To free a port, find the process that is listening on it, read what it is, and stop it with a polite signal before a forced one. On macOS, lsof -nP -iTCP:3000 -sTCP:LISTEN shows the owner. On Linux, ss -ltnp 'sport = :3000' does the same. On Windows, use netstat -ano or Get-NetTCPConnection -LocalPort 3000 -State Listen. Then run kill -TERM PID, check the port again, and keep kill -KILL PID for a process that ignores the first request.
Replace 3000 with your port and PID with the process ID from the first command. Nothing below depends on a framework or on Rig.
Confirm the symptom
Your program asked for an address and port, and another socket already holds them. Node.js documents EADDRINUSE as a failed bind because another server on the local system already occupies the address. This is a second copy of a small HTTP server on Node 22 and Linux, shortened:
Error: listen EADDRINUSE: address already in use 0.0.0.0:3000
at Server.setupListenHandle [as _listen2] (node:net:1940:16)
code: 'EADDRINUSE',
errno: -98,
syscall: 'listen',
address: '0.0.0.0',
port: 3000The address reads 0.0.0.0 because the test machine has no IPv6. Node listens on :: when IPv6 is available and on 0.0.0.0 otherwise, so with IPv6 the same failure reads :::3000.
Some tools never show the error. Vite moves to the next free port, so a dev server on 5174 usually means something else holds 5173.
Find the owner
Run the first row for your system and read the process ID. The <PID> placeholder stands for that number.
| Step | macOS | Linux | Windows PowerShell | Windows cmd |
|---|---|---|---|---|
| List the listener | lsof -nP -iTCP:3000 -sTCP:LISTEN | ss -ltnp 'sport = :3000' | Get-NetTCPConnection -LocalPort 3000 -State Listen | netstat -ano |
| Read what it is | ps -p <PID> -o pid,ppid,user,command | ps -p <PID> -o pid,ppid,user,command | tasklist /fi "PID eq <PID>" | tasklist /fi "PID eq <PID>" |
| Ask it to stop | kill -TERM <PID> | kill -TERM <PID> | taskkill /pid <PID> | taskkill /pid <PID> |
| Check again | lsof -nP -iTCP:3000 -sTCP:LISTEN | ss -ltnp 'sport = :3000' | Get-NetTCPConnection -LocalPort 3000 -State Listen | netstat -ano |
| Force it | kill -KILL <PID> | kill -KILL <PID> | taskkill /f /pid <PID> | taskkill /f /pid <PID> |
What the flags do, from each tool’s documentation:
- lsof:
-nand-Pstop name lookups,-iTCP:3000selects TCP sockets on port 3000, and-sTCP:LISTENkeeps only listeners. Without that filter, browsers connected to the port are listed too. - ss:
-llistening,-tTCP,-nnumbers,-powning process, andsport = :3000matches the local port. - netstat:
-aall connections and listening ports,-nnumbers,-othe PID. In cmd,netstat -ano | findstr /C:":3000 "narrows the list:/C:is a literal search and the trailing space stops:30000from matching. Read the Local Address column, because a remote port of 3000 matches too.
Here is the macOS and Linux sequence on the test machine, with the server running in the background:
$ lsof -nP -iTCP:3000 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 8005 root 21u IPv4 148083 0t0 TCP *:3000 (LISTEN)
$ ps -p 8005 -o pid,ppid,user,command
PID PPID USER COMMAND
8005 8002 root node server.mjsThe owner is node server.mjs, process 8005, started by process 8002. The *:3000 in the NAME column means any IPv4 address.
What was run
The lsof, ps, kill, and Node output comes from a Linux test machine. The ss and Windows commands follow each tool’s documentation, and nothing was run on a Mac. If a flag is rejected, man lsof lists what your version supports.
Decide before you stop it
Read the command and the parent before you send anything.
- Your own dev server from an earlier run: stop it.
- A database, a system service, or another user’s process: leave it and move your app to another port (see “Stop it happening again”).
- A container runtime: go to “If a container holds the port” instead of killing it.
- A process that returns after you stop it: something supervises it. Run
ps -p <PID> -o ppidand stop the parent.
Stop it: TERM before KILL
kill -TERM <PID> asks the process to terminate. A process can catch that signal and close connections and remove lock files. kill -KILL <PID> cannot be caught, blocked, or ignored, according to the Linux signal documentation, so the process gets no chance to clean up. Send TERM, wait a few seconds, and run the first command again. When it prints nothing, the port is free. Use KILL only if the same process is still listed. On the test machine, kill -TERM 8005 ended the server and lsof then printed nothing.
The one-liner skips the reading step
kill -TERM $(lsof -t -iTCP:3000 -sTCP:LISTEN) works because -t prints only process IDs. It stops every listener on the port without showing you the list, so use it only when you already know what is there.
Windows has no TERM and KILL pair in these tools. In either shell, taskkill /pid <PID> is the gentler request and /f forces the end. If it reports an error or leaves the process running, add /f. PowerShell’s Stop-Process -Id <PID> is built on the .NET Process.Kill method, which Microsoft describes as an abnormal termination to use only when necessary, so treat it as the forced option. Its -Force switch only skips the confirmation prompt for processes you do not own.
If a container holds the port
When the owner is Docker, ask Docker which container publishes the port, then stop that container:
docker ps --filter publish=3000
docker stop NAMEdocker stop sends SIGTERM to the main process and SIGKILL after a grace period, 10 seconds by default for Linux containers.
To keep the container and free the host port, change the host side of the mapping. The format is HOST:CONTAINER, so -p 3001:3000 publishes container port 3000 on host port 3001. Docker publishes to all host addresses by default, and a mapping that starts with 127.0.0.1 limits it to the Docker host. In Compose, quote the mapping, because the specification says HOST:CONTAINER should be a string so YAML does not read it as a base-60 number:
services:
web:
ports:
- "3001:3000"Stop it happening again
- Give each project its own port. The dev port planner turns a list of your services into a plan without clashes.
- Make the tool fail instead of drifting. In Vite,
--strictPortexits when the port is taken, andserver.strictPortdoes the same in the config:
export default defineConfig({
server: { port: 5173, strictPort: true },
});- Set the port in one place:
next dev -p 3001or thePORTvariable for Next.js,python manage.py runserver 127.0.0.1:8001for Django, andbin/rails server -p 3001for Rails. Defaults are on the Vite, Next.js, Django, and Rails pages. - Let the system choose in tests. In Node, port 0 gives an arbitrary unused port, which you read from
server.address().portafter the listening event.
When this does not work
The command prints nothing, but the port is taken. The owner probably belongs to another user. The lsof documentation says that in builds with its security option, only root can list everything, so run the command with sudo. A port below 1024 is a different problem: Linux needs the CAP_NET_BIND_SERVICE capability to bind it, which is a permission failure, not a conflict.
An old server answers instead of the new one. Whether two listeners can share a port on different addresses depends on the operating system. List the port and look at the NAME column:
lsof -nP -iTCP:5173 -sTCP:LISTENA row with *:5173 and a row with 127.0.0.1:5173 mean two servers. Vite’s documentation says servers on specific addresses take priority over servers on wildcard addresses such as 0.0.0.0, so if an old server sits on 127.0.0.1 and the new one listens on all addresses, requests to 127.0.0.1 still reach the old one. On Linux, a listener on 0.0.0.0 holds the port for every local address, so the second bind fails. Add 4 or 6 to the -i option, as in -i6TCP:5173, to look at one IP version.
Nothing is listening, but the bind still fails. Linux documents that a bound TCP local address stays unavailable for some time after closing, unless the socket sets SO_REUSEADDR. List connections in the time-wait state on the port:
ss -tn state time-wait '( sport = :3000 )'If rows appear and no process listens, wait for them to clear. If you wrote the server’s socket code, check whether it sets SO_REUSEADDR. Node’s documentation shows retrying listen() after a delay.
- Port in use command generator writes these commands for your port, system, and shell.
- EADDRINUSE defines the error and how to catch it in code. Network port covers ranges and TCP versus UDP.
- Common port conflicts is a short Rig page naming the usual suspects (3000, 5173, 8080, 5432, and 6379) and Rig’s reclaim fix.
- Ports, crashes, and reclaim shows how Rig reports the owner when a start fails on bind and why it prefers reclaim over a blind
kill -9. - Port already in use, without lsof is the Rig angle: ports as part of the project, and the
who_owns_portMCP tool so an agent does not guess.
Frequently asked questions
How do I find what is using a port on a Mac?
Run lsof -nP -iTCP:3000 -sTCP:LISTEN with your port in place of 3000, and read the PID column. Then run ps -p PID -o pid,ppid,user,command to see the full command.
Is kill -9 safe for freeing a port?
It works, but send it second. Signal 9 is SIGKILL, which cannot be caught, blocked, or ignored, so the process cannot clean up. Try kill -TERM first, wait, and check the port.
Why does lsof print nothing when my server says the port is taken?
The owner may belong to another user, and lsof can hide those processes when you are not root, so rerun it with sudo. Also try -i4 and -i6, and docker ps --filter publish=3000 for containers.
How do I stop Vite from moving to another port?
Pass --strictPort or set server.strictPort: true. Vite then exits when the port is in use instead of trying the next one.
How do I free a port on Windows?
In PowerShell, run Get-NetTCPConnection -LocalPort 3000 -State Listen. In cmd, read the PID column of netstat -ano. Then run taskkill /pid PID, and add /f if it reports an error or the process stays.
Can two programs listen on the same port?
Not on the same address, unless every program opts in with SO_REUSEPORT and, on Linux, runs as the same user. Different addresses can work, but on Linux a listener on 0.0.0.0 holds the port for every local address.
Sources
- Node.js: common system errors, EADDRINUSE
- Node.js: net, server.listen, default host and port 0
- lsof manual page (lsof project)
- ss(8) manual page (iproute2)
- Linux man-pages: signal(7)
- Linux man-pages: ip(7)
- Linux man-pages: socket(7)
- Microsoft: netstat
- Microsoft: taskkill
- Microsoft: tasklist
- Microsoft: findstr
- Microsoft: Get-NetTCPConnection
- Microsoft: Stop-Process
- Microsoft: Process.CloseMainWindow, remarks on Kill
- Docker: port publishing and mapping
- Docker: docker container ls, filtering by publish
- Docker: docker container stop
- Compose Specification: services, ports
- Vite: server options
- Vite: command line interface
- Next.js: next CLI
- Django: django-admin runserver
- Rails Guides: the command line
Run your whole stack from one window
Rig is a Mac app that starts, stops, and watches the processes in your project, with logs and ports in one place. Local use is free, and your source code stays on your Mac.