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
lsoflists open files - and on Unix, sockets are files.-iTCP:3000filters to TCP on port 3000.-sTCP:LISTENshows only the listener (skip this and you’ll also see every browser tab connected to the port, which is noise).-nPskips DNS and port-name lookups, so it returns instantly.
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:
- TIME_WAIT. You killed the server and restarted it within a couple of seconds; the old socket lingers briefly by design. Wait a moment, or start servers with
SO_REUSEADDR(most frameworks already do). - The listener is inside Docker. The published port is held by Docker’s own forwarder, so
lsofshowscom.dockerinstead of your app.docker psand stop the container instead of killing Docker’s proxy.
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.