Ports & dev servers

How to kill the process using a port on your Mac

One line frees the port: kill $(lsof -t -iTCP:3000 -sTCP:LISTEN). Here’s why each part matters, what to do when it won’t die or comes straight back, and how to handle Docker and Homebrew services.

The one-liner

kill $(lsof -t -iTCP:3000 -sTCP:LISTEN)
  • lsof -t prints only PIDs, which is what kill needs.
  • -iTCP:3000 picks the port. Change the number for 5173, 8080 or anything else.
  • -sTCP:LISTEN is the important bit. Without it, lsof also returns every client connected to that port, and your browser tab pointing at localhost:3000 is a client. Plenty of people have killed Chrome or Safari with lsof -ti :3000 | xargs kill.

kill with no signal sends SIGTERM, which asks the process to exit. Node, Vite, Rails and Python servers all shut down cleanly on it, flushing logs and closing database connections.

Look before you kill

If you’re not sure what is on the port, check first:

lsof -nP -iTCP:3000 -sTCP:LISTEN
ps -o pid,ppid,etime,command -p <PID>

The command column tells you whether it’s next dev from the project you meant or a Postgres you need. Our guide on finding what’s using a port goes further, including how to read the project folder from the process.

When it won’t die

Give SIGTERM a couple of seconds. If the process is still listening, force it:

kill -9 <PID>

SIGKILL (-9) can’t be caught, so the process gets no chance to clean up. Use it on a hung dev server, not on a database, which can be left needing recovery.

If kill says Operation not permitted, the process isn’t yours. Check the owner with ps -o user,pid,command -p <PID> and use sudo kill <PID> only if you know what it is.

When it comes straight back

You kill it, and two seconds later the port is taken again with a new PID. Something is restarting it. Find the parent:

ps -o ppid= -p <PID> | xargs ps -o pid,command -p
Parent you seeWhat to stop instead
nodemon, tsx watch, turbo, concurrentlyThe watcher process, or close its terminal tab
pm2pm2 stop all or pm2 delete <name>
launchd (PID 1) with a Homebrew servicebrew services stop postgresql@16 (use the name from brew services list)
launchd with a LaunchAgentlaunchctl bootout gui/$(id -u)/<label>

A PID 1 parent with no service behind it usually means an orphan: its terminal closed but it kept running. See finding orphaned Node processes.

When it’s Docker

If lsof names com.docker.backend, OrbStack or limactl, killing it would take down every container. Stop the container that published the port instead:

docker ps --filter publish=5432
docker stop <container-id>

For a Compose project, docker compose down in its folder stops the whole stack.

When it’s macOS itself

On ports 5000 and 7000, the listener is often ControlCenter, which is the AirPlay Receiver. Don’t kill it, it just restarts. Turn it off in System Settings › General › AirDrop & Handoff › AirPlay Receiver, or run your app on another port. More on that in address already in use on Mac and on the port 5000 page.

A shell function to keep

Add this to ~/.zshrc so killport 3000 does the safe thing and tells you what it stopped:

killport() {
  local pids
  pids=$(lsof -t -iTCP:"$1" -sTCP:LISTEN) || { echo "Nothing listening on $1"; return 1; }
  ps -o pid,command -p ${(f)pids}
  kill ${(f)pids}
}

Doing this with Stat Space

Stat Space lists every listening port in the menu bar with its framework, project folder and launcher, so you can tell the node you want gone from the one you don’t. Stopping a server from the Ports tab always asks first, and system processes are refused.

From the terminal, statspace kill 3000 does the same lookup, shows what it’s about to stop and asks before sending the signal (-y skips the prompt, --force sends SIGKILL).

Questions

Is npx kill-port 3000 safe to use?

It works, but it downloads a package and kills every process with a connection on that port, not only the server. The lsof -sTCP:LISTEN version targets just the listener and needs nothing installed.

Why does the port stay busy for a few seconds after the process dies?

It usually doesn’t for a listening socket, but a client-side connection can sit in TIME_WAIT briefly. If lsof -iTCP:3000 -sTCP:LISTEN returns nothing, the port is free for a new server even if netstat still lists old connections.

Do I need sudo to kill a process on a port?

Only if the process belongs to root or another user, such as a system Postgres or a service started with sudo. Your own dev servers can be stopped without sudo.

How do I kill whatever is on several ports at once?

List them in one lsof call: kill $(lsof -t -iTCP:3000,5173,8080 -sTCP:LISTEN). Check the list first by running the lsof part on its own.

Get started

Know what your Mac is doing, without adding to it.

Free, no account. Pro is $9.99 once for the first 100 buyers.