← Field notes

September 1, 2026 · 4 min read

How to kill the process on a port on macOS

The two commands that free any busy port on a Mac: lsof to find the process, kill to stop it. Plus what EADDRINUSE really means and why the port fills up again.

You start a dev server and get the error every developer has memorized without ever wanting to:

Error: listen EADDRINUSE: address already in use :::3000

Something is already sitting on port 3000. Here’s the fastest safe way to deal with it on macOS - find the process, look at it, then stop it.

The short version

lsof -nP -iTCP:3000 -sTCP:LISTEN
kill <PID>

The first command tells you what is listening on the port and its PID. The second asks it to quit. That’s the whole trick - everything below is detail, safety, and the reason it keeps happening.

What “address already in use” actually means

Only one process can listen on a given TCP port at a time. EADDRINUSE (or Address already in use, or bind: address already in use - same thing from Node, Python, Rails, Docker, anything) means the bind failed because another process (often a previous copy of your own server that never fully exited) is still holding the port.

The fix is never to reboot, and rarely to change ports. It’s to find the holder and stop it.

Find the process using the port

lsof -nP -iTCP:3000 -sTCP:LISTEN

Typical output:

COMMAND   PID           USER   FD   TYPE  DEVICE  SIZE/OFF NODE NAME
node    41562  mariojankovic  23u  IPv6  0x5e…      0t0    TCP  *:3000 (LISTEN)

That’s everything you need: it’s node, PID 41562. If the command prints nothing but the port still errors, jump to the port is “busy” but nothing is listening.

Prefer netstat? netstat -anv | grep LISTEN | grep 3000 works, but its output is worse and you still need the PID column. lsof is the right tool on a Mac.

Look before you kill

One extra command has saved us from killing the wrong thing many times:

ps -p 41562 -o pid,ppid,etime,command

Now you know which node process this is - the orphaned dev server from yesterday, or the one your teammate’s demo is running on. Ports get reused; assumptions get punished.

Kill it - gently first

kill 41562        # SIGTERM: "please exit" - servers clean up and release the port

If the port is still held a few seconds later:

kill -9 41562     # SIGKILL: no cleanup, no argument

Use -9 as the second resort, not the first. SIGTERM lets a server close sockets, flush logs, and remove lock files; SIGKILL guarantees none of that happens. You almost never need sudo for your own dev servers - if you do, the process belongs to another user (or root), and it’s worth knowing that before killing it.

The one-liner

When you already know you want the port free:

kill $(lsof -tiTCP:3000 -sTCP:LISTEN)

-t makes lsof print just the PID. Make it a shell function once and stop thinking about it:

# ~/.zshrc
killport() { kill $(lsof -tiTCP:"$1" -sTCP:LISTEN) }

Then killport 3000, killport 5173, killport 8080. (The popular npx kill-port does the same thing with a 10-second npm detour - the function is instant and works offline.)

The port is “busy” but nothing is listening

Two cases show up when lsof comes back empty:

Why your ports keep filling up

Here’s the part nobody’s tutorial mentions: in 2026 the process on the port is very often something you didn’t start. An AI coding agent spins up a worktree, boots the dev server, serves it on 3000 - and when the branch merges, nobody sends the SIGTERM. Multiply by a few agents and a week of work, and every port you reach for is taken by a server whose branch no longer exists.

You can lsof your way through that every day, or you can see the whole board at once. Clueprint shows every listening port on your Mac grouped by project and branch, tells you which agent chat launched the process, and frees a port with a right-click - the same safe SIGTERM-first sequence as above, with the “look before you kill” step built in. The ports that answer HTTP even become clickable links, so “what is running on 10400 again?” stops being a terminal question.

Cheat sheet

lsof -nP -iTCP:PORT -sTCP:LISTEN     # who's on the port
ps -p PID -o pid,etime,command       # what is it, how long has it run
kill PID                             # polite
kill -9 PID                          # last resort
kill $(lsof -tiTCP:PORT -sTCP:LISTEN)  # one-liner

Works the same for port 3000, 5173, 8000, 8080 - and for EADDRINUSE, address already in use, and every other spelling of the same complaint.