12 min read
How to Find and Kill the Process Using a Port on Mac (Ports 3000, 5000, 8080)
Port 3000 or 5000 already in use on your Mac? Find the process with lsof, stop it safely, and fix the cases where AirPlay, Docker or a service holds the port.
- Developers
- How-to
- Troubleshooting

EADDRINUSE: address already in use or [Errno 48] Address already in use: your dev server can't start because something already holds the port. On a Mac the fix is two commands, one to see which process is listening and one to stop it. This guide gives you both, explains why the one-liner everyone copies can also close your browser, and covers the Mac-specific cases: AirPlay Receiver on ports 5000 and 7000, Docker, Homebrew services, and processes owned by root.
The short version
- Find the listener:
lsof -nP -iTCP:3000 -sTCP:LISTEN. The number in the PID column is the process holding the port. - Stop it politely:
kill <PID>sends SIGTERM, which lets the process clean up. Only if it's still listening a few seconds later, usekill -9 <PID>. - Keep
-sTCP:LISTENin any one-liner. Without it,lsof -ti:3000also returns apps that are merely connected to the port, such as your browser. - Port 5000 or 7000 held by
ControlCeis macOS's AirPlay Receiver. Turn it off in System Settings → General → AirDrop & Continuity, or run your app on another port. - If it comes straight back, something is restarting it:
brew services, a launchd agent, a Docker container or pm2. Stop it there, not withkill. - Can't see or kill it? It probably belongs to root or another user. Add
sudo.
1. Find what's listening on the port
lsof -nP -iTCP:3000 -sTCP:LISTEN
On a Mac with a Next.js dev server running, that prints one line:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 38917 you 13u IPv4 0x9bf10a14d39967c5 0t0 TCP *:3000 (LISTEN)
What each part of the command does (all from man lsof on macOS 27.2):
-iTCP:3000selects network sockets using TCP port 3000, at either end of a connection.-sTCP:LISTENkeeps only sockets in the LISTEN state: the server, not the connections to it.-nand-Pstop lsof from turning addresses into host names and port numbers into service names. Without-P, port 3000 shows up ashbciand 5000 ascommplex-main, because that's what/etc/servicescalls them.
The NAME column also tells you who can reach the server. *:3000 means every network interface, so other devices on your Wi-Fi can connect (firewall permitting). 127.0.0.1:3000 or [::1]:3000 means this Mac only.
See every listening port at once
lsof -nP -iTCP -sTCP:LISTEN
lsof cuts command names to nine characters, so ControlCenter appears as ControlCe and Docker's backend as com.docke. Add +c 0 to print names in full:
lsof -nP +c 0 -iTCP -sTCP:LISTEN
lsof does more than ports: the startup disk guide uses it to find deleted files that a running app still holds open.
Find out what the PID actually is
node or Python doesn't say much when you have five projects open. ps shows the full command, its parent and how long it has been running:
ps -p 38917 -o pid,ppid,etime,command
On our test Mac, that revealed the listener was next-server (v16.3.5), a child of a next dev process that had been running for 27 minutes. An ETIME of hours or days usually means a server left over from an earlier session. The PPID tells you what started it, which matters in section 5.
Processes owned by root or another user
Without sudo, lsof on macOS only shows sockets of processes running as you. A root daemon on the port simply doesn't appear, which is confusing when the error says the port is taken and lsof says nothing is there. Run it again with sudo:
sudo lsof -nP -iTCP:80 -sTCP:LISTEN
macOS's netstat is a useful cross-check because it shows a process:pid column for every socket, including root's, without sudo. On our 27.2 Mac it listed a root-owned launchd listener that plain lsof missed. Addresses use a dot before the port (*.3000):
netstat -anv -p tcp | grep LISTEN
UDP ports
UDP has no listening state, so drop the -s filter:
lsof -nP -iUDP:5353
2. Stop it: SIGTERM first, SIGKILL only if you must
The cleanest stop is Ctrl-C in the terminal that's running the server. It sends SIGINT to the whole foreground process group, so npm run dev, the Node processes it spawned and any file watchers all get the signal at once.
Watch for Ctrl-Z. It suspends a process rather than stopping it, and a suspended server keeps its port. Run jobs -l in that terminal; if the server is listed as suspended, type fg and press Ctrl-C, or run kill %1. In zsh, the Mac's default shell, kill on a stopped job also sends it a continue signal so it can act on the TERM (man zshbuiltins).
From another terminal, use the PID:
kill 38917 # SIGTERM: asks the process to exit
sleep 3
lsof -nP -iTCP:3000 -sTCP:LISTEN # still there?
kill -9 38917 # SIGKILL: only if it is
The order matters. A process can catch SIGTERM and use it to close connections, finish writes and remove its lock and PID files. SIGKILL is the one signal a process can't catch or ignore (man kill says as much), so the process vanishes mid-step. For a Node dev server that rarely matters. For a database it does: the PostgreSQL documentation says "It is best not to use SIGKILL to shut down the server," because it leaves shared memory and child processes behind.
If kill reports "operation not permitted", the process isn't yours, and only root can signal other users' processes. Check what it is with ps first, then use sudo kill <PID>.
A one-liner that only hits the server
kill $(lsof -tiTCP:3000 -sTCP:LISTEN)
-t makes lsof print bare PIDs. If nothing is listening, kill just prints a usage message and does nothing.
A reusable function
Paste this into ~/.zshrc (it also works in bash), open a new terminal, and killport 3000 does the polite-then-forceful sequence for you:
killport() {
local -a pids
pids=( $(lsof -tiTCP:"$1" -sTCP:LISTEN) )
if [ ${#pids[@]} -eq 0 ]; then
echo "Nothing you can see is listening on port $1"
return 1
fi
echo "Asking ${pids[*]} to quit (SIGTERM)"
kill "${pids[@]}"
sleep 3
pids=( $(lsof -tiTCP:"$1" -sTCP:LISTEN) )
if [ ${#pids[@]} -gt 0 ]; then
echo "Still running, forcing ${pids[*]} (SIGKILL)"
kill -9 "${pids[@]}"
fi
}
Don't point it at 5000 or 7000 while AirPlay Receiver is on; section 4 explains why.
3. Why the popular one-liner can close your browser
The answer most people copy is kill -9 $(lsof -ti:3000). The problem is -i :3000: it matches any socket with 3000 at either end, so it returns the server and every client connected to it. Here's port 3000 on our test Mac while a browser had the dev site open:
lsof -nP -iTCP:3000
COMMAND PID USER FD TYPE ... NAME
firefox 1053 you 125u IPv4 ... TCP 127.0.0.1:59061->127.0.0.1:3000 (ESTABLISHED)
node 38917 you 13u IPv4 ... TCP *:3000 (LISTEN)
node 38917 you 35u IPv4 ... TCP 127.0.0.1:3000->127.0.0.1:59061 (ESTABLISHED)
lsof -ti:3000 printed both 1053 and 38917, so the one-liner would have force-quit the browser along with the server, taking every open tab with it. With -sTCP:LISTEN added, lsof returned only 38917.
Next.js agrees. When next dev finds its default port taken, it looks up the culprit with lsof -ti:3000 -sTCP:LISTEN (you can see it in next/dist/server/lib/start-server.js). The npm package kill-port also filters for listeners, but version 2.0.1, the one npx kill-port 3000 fetches at the time of writing, sends kill -9 with no SIGTERM first.
4. Port 5000 or 7000 in use: it's AirPlay Receiver
Since macOS Monterey, a Mac can receive AirPlay, and the ControlCenter process listens on ports 5000 and 7000 to do it. On our macOS 27 Golden Gate test Mac:
ControlCe 685 you 9u IPv4 ... TCP *:7000 (LISTEN)
ControlCe 685 you 11u IPv4 ... TCP *:5000 (LISTEN)
(plus matching IPv6 rows). Port 5000 is Flask's default, so flask run fails with OSError: [Errno 48] Address already in use, and a Node server reports Error: listen EADDRINUSE: address already in use :::5000.
Don't kill ControlCenter. It runs Control Center and several menu bar items, and its launchd job (/System/Library/LaunchAgents/com.apple.controlcenter.plist) tells macOS to relaunch it whenever it exits abnormally. It comes straight back, ports and all. You have two real options:
- Turn off AirPlay Receiver. Open System Settings → General → AirDrop & Continuity and switch off AirPlay Receiver. That's the pane's name in Apple's guide for macOS Tahoe 26 and Golden Gate; Ventura to Sequoia called it AirDrop & Handoff, and Monterey kept it under Sharing. ControlCenter stops listening on both ports. You lose the ability to AirPlay from an iPhone to this Mac until you switch it back on.
- Move your app.
flask run --port 5001, orPORT=5001for most Node servers. Flask's own docs suggest exactly these two choices.
5. It comes back after you kill it
If a new PID appears on the same port seconds after you stopped it, something is supervising it. ps -o pid,ppid,command -p <PID> on the new process shows its parent, and that usually points at one of these.
Homebrew services
Databases installed with Homebrew often run as login services. List them, then stop the one on your port by the name shown:
brew services list
brew services stop postgresql@17
stop also unregisters it from starting at login. To stop it just for now, use brew services kill, which keeps the registration (Homebrew manual).
launchd agents
Many app helpers run as launchd jobs, and a job marked KeepAlive is restarted whenever it dies. launchctl list shows the PID, last exit status and label of each of your jobs:
launchctl list | grep -i <name>
launchctl bootout gui/$(id -u)/<label>
bootout unloads the job until it's loaded again, usually at your next login. The lasting fix is the owning app's own setting, or quitting that app. System-wide daemons need sudo launchctl bootout system/<label>.
Docker containers
On a Mac, published container ports are held by Docker Desktop's com.docker.backend process (Docker's networking docs). It proxies the networking for all your containers, so killing it hits every container, not just the one you meant. Stop the container instead:
docker ps # the PORTS column shows 0.0.0.0:5432->5432/tcp
docker stop <container-name> # or: docker compose down, in the project folder
A container with the restart policy always comes back the next time Docker starts (Docker docs); docker update --restart no <container-name> changes that. Also note that -p 5432:5432 publishes to all interfaces by default. Use -p 127.0.0.1:5432:5432 if only your Mac needs it.
pm2 and other process managers
pm2 restarts apps that crash or exit by default (its autorestart setting is true), so kill just triggers a restart. Use pm2 list, then pm2 stop <name> or pm2 delete <name>.
Dev servers that quietly change port
Some tools don't fail on a taken port; they move. Vite tries the next free port after 5173 unless strictPort is set. Next.js dev does the same when you haven't set -p or PORT, printing "Port 3000 is in use by process 38917, using available port 3001 instead." If your app is suddenly on 3001, an old server still owns 3000.
Common ports on a developer Mac
| Port | Default for | Notes |
|---|---|---|
| 3000 | Next.js (next dev, next start), Rails bin/rails server | Next.js dev moves to 3001 if you didn't pick the port |
| 4200 | Angular ng serve | |
| 5173 / 4173 | Vite dev server / vite preview | Vite moves up a port unless strictPort |
| 5000 | Flask flask run | Also AirPlay Receiver (ControlCenter) |
| 7000 | AirPlay Receiver (ControlCenter) | |
| 8000 | Django runserver, python -m http.server | |
| 8080 | Spring Boot | Common "alternate HTTP" port |
| 5432 | PostgreSQL | Often a Homebrew service or Docker container |
| 6379 | Redis | Often a Homebrew service or Docker container |
| 27017 | MongoDB | |
| 11434 | Ollama | Bound to 127.0.0.1 by default (Ollama FAQ) |
| 1234 | LM Studio's local server | The port LM Studio's docs use in examples |
If you run local models, they're also among the biggest things on your disk; the AI tools cleanup guide shows where Ollama and LM Studio keep them.
Ports in Disk Clean AI's menu bar
Disk Clean AI is mainly a storage cleaner, and it covers the developer clutter: its Quick Wins find node_modules, Xcode DerivedData, iOS Simulators and build folders such as .next and dist, and everything you approve goes to the Trash with one-click Undo. The developer cleanup guide covers the same ground by hand.
It also has System Tools and a menu-bar monitor with a listening-ports list built on the same lsof -nP +c 0 -iTCP -sTCP:LISTEN command from this guide. It merges IPv4 and IPv6 rows and shows each port's process, PID and whether it's bound to localhost or all interfaces. UDP is an optional checkbox. The stop button sends SIGTERM and escalates to SIGKILL only if the process is still running a moment later. Right-click to send only SIGTERM, only SIGKILL, or to kill as administrator through macOS's own password prompt, or type a port number into the Kill Port field. ControlCenter on 5000 or 7000 gets a hint pointing to AirPlay Receiver instead of a kill. Like lsof without sudo, the list only shows processes running as you. Every feature is included in the 7-day free trial; after that, a license is a one-time payment with no subscription: $19 at launch (usually $39), for up to 3 Macs.
FAQ
How do I see what's using a port on a Mac?
Run lsof -nP -iTCP:3000 -sTCP:LISTEN, replacing 3000 with your port. The COMMAND and PID columns name the process. If nothing shows but the port is still taken, run it again with sudo, because a process owned by root or another user is hidden from a normal lsof.
How do I kill the process on port 3000?
kill $(lsof -tiTCP:3000 -sTCP:LISTEN) asks the listener to exit. If it's still there after a few seconds, repeat with kill -9. If you can find the terminal running it, Ctrl-C there is cleaner still.
Why is port 5000 in use by ControlCenter?
That's AirPlay Receiver, which listens on ports 5000 and 7000. Turn it off in System Settings → General → AirDrop & Continuity, or run your app on another port. Killing ControlCenter doesn't help, because macOS relaunches it immediately.
Why does the process come back after I kill it?
Something supervises it: a Homebrew service, a launchd job with KeepAlive, a Docker container with a restart policy, or pm2. Stop it through that tool (brew services stop, launchctl bootout, docker stop, pm2 stop) and it stays stopped.
Is kill -9 safe?
It's safe for the system, but rough on the process: SIGKILL can't be caught, so the program gets no chance to save state, close connections or remove lock files. Use plain kill (SIGTERM) first and keep -9 for processes that ignore it. Avoid it on databases, which can need recovery afterwards.
How do I list all listening ports on macOS?
lsof -nP -iTCP -sTCP:LISTEN lists your own processes' listening TCP ports; add +c 0 for full process names and sudo to include everything. netstat -anv -p tcp | grep LISTEN shows every listener with a process:pid column, no sudo needed. If one of them turns out to be a runaway process eating memory too, the out of application memory guide covers what to check next.


