← All articles

Guide · · 6 min read

Port already in use on Mac: find and kill it

“Port already in use” on a Mac means another process is already listening on the port your app wants. The fix takes two commands: find the process with lsof, then kill the process on that port. This guide gives the exact commands, the one mistake most copy-paste one-liners make, the two cases where kill does not work (AirPlay and Docker), and how to stop the error from coming back.

In short

  • Find who listens: lsof -nP -iTCP:3000 -sTCP:LISTEN.
  • Kill only that process: kill $(lsof -t -iTCP:3000 -sTCP:LISTEN), and kill -9 only if it refuses to stop.
  • Port 5000 or 7000 taken by ControlCenter? That is AirPlay Receiver: turn it off or use another port.
  • Port taken by Docker (com.docke in lsof)? Stop the container with docker stop, not the process.
  • For good: give each project its own fixed port, or let Tarmac assign and remember one per app.

What the error looks like

The wording depends on the tool, the cause is the same:

Error: listen EADDRINUSE: address already in use :::3000
OSError: [Errno 48] Address already in use

The first comes from Node.js (Express, Next.js, Nuxt, Vite and friends), the second from Python (Flask, Django, FastAPI with uvicorn). Some dev servers do not fail: they quietly move to the next free port, and you end up on localhost:3001 wondering why your callback URL broke.

Step 1: find the process on the port

lsof -nP -iTCP:3000 -sTCP:LISTEN

-iTCP:3000 selects TCP connections on port 3000, -sTCP:LISTEN keeps only the process that is listening (the server), -n and -P skip name lookups so the answer is instant. You get something like:

COMMAND   PID   USER   FD   TYPE  DEVICE  SIZE/OFF NODE NAME
node    48213  sam    23u  IPv6  0x...       0t0  TCP *:3000 (LISTEN)

The number in the PID column is the one you need. Before you kill it, check what it is:

ps -p 48213 -o command

If it is a dev server you forgot, carry on. If it is something you do not recognise, look twice: it may be a database, a container or a system service.

To see every port your Mac is listening on, drop the port number:

lsof -nP -iTCP -sTCP:LISTEN

Step 2: kill the process on that port

kill 48213

kill sends a polite stop signal (SIGTERM), which lets the server close its files and connections. If the process is still there a few seconds later:

kill -9 48213

-9 (SIGKILL) cannot be ignored, but the process gets no chance to clean up. Use it second, not first.

The one-liner, done right

Many tutorials suggest lsof -ti :3000 | xargs kill. The problem: without -sTCP:LISTEN, lsof also returns processes that are merely connected to port 3000, such as your browser with the app open. That command can close Chrome along with the server. This version targets only the listener:

kill $(lsof -t -iTCP:3000 -sTCP:LISTEN)

-t prints only the PIDs, which is what kill expects.

When kill does not work

Port 5000 or 7000, owned by ControlCenter. lsof shows it as ControlCe, because it cuts command names short. Since macOS Monterey, the AirPlay Receiver listens on ports 5000 and 7000, and 5000 is Flask’s default port. Killing ControlCenter is pointless: macOS starts it again. Either turn the receiver off in System Settings → General → AirDrop & Handoff → AirPlay Receiver, or run your app on another port:

flask run --port 5001

A port owned by Docker. If lsof shows a com.docke process (Docker’s backend), the port is published by a container, and killing Docker’s process would stop all of them. Find the container and stop it instead:

docker ps --format '{{.Names}}\t{{.Ports}}'
docker stop <container-name>

The same applies to OrbStack and Colima. A local Supabase stack, for example, publishes a range of ports starting at 54321; supabase stop frees them all.

The process comes back. A process restarted by a supervisor (launchd, pm2, nodemon, a docker compose restart policy) will reappear. Stop it through the tool that runs it.

On Windows

In PowerShell, netstat -ano | findstr :3000 shows the PID in the last column, and taskkill /PID <PID> /F stops it. Inside WSL 2, the Linux commands above work as on a Mac.

Stop it from coming back

Killing processes treats the symptom. The cause is that several projects want the same few ports (3000, 5173, 8000) and nothing remembers which one runs where. Two fixes:

  1. Give each project its own port, written down in the project: PORT=3001 in .env for Next.js or Express, server.port with strictPort: true in vite.config.ts for Vite, so it fails loudly instead of drifting to another port.
  2. Let a tool hand out ports. That is what Tarmac does.

How Tarmac handles ports

Tarmac is a free, open source local dashboard for macOS and Windows (WSL 2) that finds the projects in your code folders and starts them for you:

  • A free port per app, kept from one run to the next. Apps started by Tarmac get a port from 4100 to 4999, a range that avoids the usual 3000, 5173 and the Supabase ports. The app on 4123 today is on 4123 tomorrow.
  • Apps started elsewhere are detected. A server already running on an app’s port, started from a terminal or a script, shows as Running · outside Tarmac instead of failing with EADDRINUSE, and Tarmac adopts it.
  • Free the ports after a crash. One button stops what an app left behind on its ports.
  • Logs and status next to it, plus Docker containers grouped by project and local Supabase instances with their ports.

The background is in Run ten local projects without a single port conflict.

Next steps

Keep the two commands at hand (lsof -nP -iTCP:<port> -sTCP:LISTEN, then kill <PID>), and if you juggle several projects, install Tarmac so the question stops coming up.

Sources: lsof manual page; AirPlay Receiver on port 5000 since macOS Monterey (Stack Overflow); docker ps reference. Checked on 6 November 2026.