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.

Updated 8 min read

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:

Text
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: 3000

The 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.

StepmacOSLinuxWindows PowerShellWindows cmd
List the listenerlsof -nP -iTCP:3000 -sTCP:LISTENss -ltnp 'sport = :3000'Get-NetTCPConnection -LocalPort 3000 -State Listennetstat -ano
Read what it isps -p <PID> -o pid,ppid,user,commandps -p <PID> -o pid,ppid,user,commandtasklist /fi "PID eq <PID>"tasklist /fi "PID eq <PID>"
Ask it to stopkill -TERM <PID>kill -TERM <PID>taskkill /pid <PID>taskkill /pid <PID>
Check againlsof -nP -iTCP:3000 -sTCP:LISTENss -ltnp 'sport = :3000'Get-NetTCPConnection -LocalPort 3000 -State Listennetstat -ano
Force itkill -KILL <PID>kill -KILL <PID>taskkill /f /pid <PID>taskkill /f /pid <PID>

What the flags do, from each tool’s documentation:

  • lsof: -n and -P stop name lookups, -iTCP:3000 selects TCP sockets on port 3000, and -sTCP:LISTEN keeps only listeners. Without that filter, browsers connected to the port are listed too.
  • ss: -l listening, -t TCP, -n numbers, -p owning process, and sport = :3000 matches the local port.
  • netstat: -a all connections and listening ports, -n numbers, -o the PID. In cmd, netstat -ano | findstr /C:":3000 " narrows the list: /C: is a literal search and the trailing space stops :30000 from 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:

Text
$ 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.mjs

The 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 ppid and 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:

Terminal
docker ps --filter publish=3000
docker stop NAME

docker 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:

YAML
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, --strictPort exits when the port is taken, and server.strictPort does the same in the config:
TypeScript
export default defineConfig({
  server: { port: 5173, strictPort: true },
});
  • Set the port in one place: next dev -p 3001 or the PORT variable for Next.js, python manage.py runserver 127.0.0.1:8001 for Django, and bin/rails server -p 3001 for 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().port after 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:

Terminal
lsof -nP -iTCP:5173 -sTCP:LISTEN

A 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:

Terminal
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.

More on this

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

  1. Node.js: common system errors, EADDRINUSEOfficial documentation, checked 13 October 2026
  2. Node.js: net, server.listen, default host and port 0Official documentation, checked 13 October 2026
  3. lsof manual page (lsof project)Official documentation, checked 13 October 2026
  4. ss(8) manual page (iproute2)Official documentation, checked 13 October 2026
  5. Linux man-pages: signal(7)Official documentation, checked 13 October 2026
  6. Linux man-pages: ip(7)Official documentation, checked 13 October 2026
  7. Linux man-pages: socket(7)Official documentation, checked 13 October 2026
  8. Microsoft: netstatOfficial documentation, checked 13 October 2026
  9. Microsoft: taskkillOfficial documentation, checked 13 October 2026
  10. Microsoft: tasklistOfficial documentation, checked 13 October 2026
  11. Microsoft: findstrOfficial documentation, checked 13 October 2026
  12. Microsoft: Get-NetTCPConnectionOfficial documentation, checked 13 October 2026
  13. Microsoft: Stop-ProcessOfficial documentation, checked 13 October 2026
  14. Microsoft: Process.CloseMainWindow, remarks on KillOfficial documentation, checked 13 October 2026
  15. Docker: port publishing and mappingOfficial documentation, checked 13 October 2026
  16. Docker: docker container ls, filtering by publishOfficial documentation, checked 13 October 2026
  17. Docker: docker container stopOfficial documentation, checked 13 October 2026
  18. Compose Specification: services, portsOfficial documentation, checked 13 October 2026
  19. Vite: server optionsOfficial documentation, checked 13 October 2026
  20. Vite: command line interfaceOfficial documentation, checked 13 October 2026
  21. Next.js: next CLIOfficial documentation, checked 13 October 2026
  22. Django: django-admin runserverOfficial documentation, checked 13 October 2026
  23. Rails Guides: the command lineOfficial documentation, checked 13 October 2026

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.

Related

https://userig.app/learn/find-and-free-a-port