All guides

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
Disk Clean AI's Treemap view of a 128.8 GB Macintosh HD, with Users, System, Applications (Xcode.app the largest block), Library/Developer, private and opt/homebrew drawn as rectangles sized by bytes, and a tooltip showing /System/Library at 33.64 GB.

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, use kill -9 <PID>.
  • Keep -sTCP:LISTEN in any one-liner. Without it, lsof -ti:3000 also returns apps that are merely connected to the port, such as your browser.
  • Port 5000 or 7000 held by ControlCe is 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 with kill.
  • 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:3000 selects network sockets using TCP port 3000, at either end of a connection.
  • -sTCP:LISTEN keeps only sockets in the LISTEN state: the server, not the connections to it.
  • -n and -P stop lsof from turning addresses into host names and port numbers into service names. Without -P, port 3000 shows up as hbci and 5000 as commplex-main, because that's what /etc/services calls 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.

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:

  1. 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.
  2. Move your app. flask run --port 5001, or PORT=5001 for 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

PortDefault forNotes
3000Next.js (next dev, next start), Rails bin/rails serverNext.js dev moves to 3001 if you didn't pick the port
4200Angular ng serve
5173 / 4173Vite dev server / vite previewVite moves up a port unless strictPort
5000Flask flask runAlso AirPlay Receiver (ControlCenter)
7000AirPlay Receiver (ControlCenter)
8000Django runserver, python -m http.server
8080Spring BootCommon "alternate HTTP" port
5432PostgreSQLOften a Homebrew service or Docker container
6379RedisOften a Homebrew service or Docker container
27017MongoDB
11434OllamaBound to 127.0.0.1 by default (Ollama FAQ)
1234LM Studio's local serverThe 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.

Disk Clean AI's Find Downloads tool showing 273 items (1.28 GB), with 286.3 MB older than 30 days, 738.4 MB of installers marked usually safe once installed, and the downloads listed by size.

11 min read

Mac Trash Won't Empty? How to Force Empty It Safely

Trash won't empty on your Mac? Fix “item in use”, locked files, permission errors and error -8003, find what's holding a file, and force-empty it safely.

  • Troubleshooting
  • How-to
  • Storage