Network

How to Kill a Process on a Port on Mac (Port Already in Use)

Published Nov 2, 2026
Tested on a real Mac. Ran lsof, kill and kill -9 against test servers started in a scratch folder on macOS 26.6.2 (zsh 5.9), and checked ports 5000/7000; the AirPlay Receiver setting was not changed. Test machine: MacBook Air (M3, 16 GB), macOS Tahoe 26.6.2, zsh 5.9.
On this page
  1. Step 1: Find what is using the port
  2. Step 2: Kill it by PID
  3. kill vs kill -9
  4. Port 5000 or 7000 in use? It’s probably AirPlay
  5. A reusable killport function for zsh
  6. Common questions

To free a port on a Mac, find the process that is listening on it, then kill that process by its PID. This one line does both for port 8080:

kill $(lsof -ti tcp:8080 -sTCP:LISTEN)

If you want to see what you are about to kill first (you should), run lsof -nP -iTCP:8080 -sTCP:LISTEN. Swap 8080 for your port. If the port is 5000 or 7000, read the AirPlay section below before you kill anything.

Step 1: Find what is using the port

I started a test server with python3 -m http.server 8765 and then tried to start a second one on the same port. Python refused:

OSError: [Errno 48] Address already in use

Node reports the same thing as EADDRINUSE. Now look up the owner. The command most guides give is lsof -i :PORT:

lsof -i :8765
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  94683  you    5u  IPv6 0xf51e5a797274a933      0t0  TCP *:ultraseek-http (LISTEN)

Notice the NAME column. It says ultraseek-http, not 8765. By default, lsof swaps port numbers for service names from /etc/services. Port 8765 is listed there as “Ultraseek HTTP”, 8080 shows up as http-alt, and 5000 as commplex-main. If you search the output for your port number, you can miss the line entirely.

The precise form adds three things:

lsof -nP -iTCP:8765 -sTCP:LISTEN
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  94683  you    5u  IPv6 0xf51e5a797274a933      0t0  TCP *:8765 (LISTEN)
FlagWhat it does
-nShow IP addresses, not hostnames (also faster)
-PShow port numbers, not service names
-iTCP:8765Only TCP sockets on port 8765
-sTCP:LISTENOnly the socket that is listening, i.e. the server

You don’t need sudo for your own processes. If lsof prints nothing but the port is still busy, the owner is another user or a system service, and sudo lsof -nP -iTCP:8765 -sTCP:LISTEN will show it (not run here).

Step 2: Kill it by PID

lsof -t prints only the PID, which is handy for scripts:

lsof -ti :8765
94683

Then pass it to kill:

kill $(lsof -ti tcp:8765 -sTCP:LISTEN)

Run the lsof command again. No output (exit code 1) means the port is free.

Gotcha: lsof -ti :PORT also returns clients

This is the part most answers skip. While my server was running, I opened a second Python process that simply connected to it, like a browser tab would. Then I ran the short form again:

lsof -ti :8765
94683
94721

Two PIDs. The second one is the client. lsof -nP -i :8765 shows why:

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  94683  you    5u  IPv6 0xf51e5a797274a933      0t0  TCP *:8765 (LISTEN)
Python  94683  you    6u  IPv6 0xdfd38808aa9cc483      0t0  TCP 127.0.0.1:8765->127.0.0.1:62878 (ESTABLISHED)
Python  94721  you    3u  IPv4 0x2b16794ca1c38ec1      0t0  TCP 127.0.0.1:8765->127.0.0.1:62878 (ESTABLISHED)

So kill $(lsof -ti :3000) can also kill your browser, your API client, or a database GUI that happens to be connected to that port. Adding -sTCP:LISTEN returned only 94683, the server. Always include it.

kill vs kill -9

Plain kill sends SIGTERM, which asks the process to shut down cleanly. My Python test server exited right away on it.

kill -9 sends SIGKILL. The kernel stops the process immediately, and the process cannot catch or ignore it. Use it only when plain kill didn’t work.

To test this, I wrote a small Python server that catches SIGTERM and ignores it. After a plain kill, it was still listening two seconds later:

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  94850  you    3u  IPv4 0xf51e5a797274a933      0t0  TCP 127.0.0.1:8765 (LISTEN)

Its log said got SIGTERM, ignoring. kill -9 94850 removed it at once, and the next lsof check returned nothing.

So the order is: kill PID, wait a second or two, check again, then kill -9 PID only if it’s still there. A process killed with -9 can leave behind lock files or half-written data, so don’t make it your default.

Also note what happens when nothing is listening. In zsh, $(...) expands to nothing and kill complains:

zsh:kill:1: not enough arguments

That’s harmless. It just means the port was already free.

Port 5000 or 7000 in use? It’s probably AirPlay

On macOS Monterey and later, the AirPlay Receiver feature listens on ports 5000 and 7000. Flask’s default port is 5000, so this trips up a lot of people. I checked both ports on my test Mac (macOS Tahoe 26.6.2):

lsof -nP -iTCP:5000 -sTCP:LISTEN
lsof -nP -iTCP:7000 -sTCP:LISTEN
COMMAND    PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ControlCe 1027  you   11u  IPv4 0xa891f5c9f704fb7c      0t0  TCP *:5000 (LISTEN)
ControlCe 1027  you   12u  IPv6 0x584b47cc38cb6abd      0t0  TCP *:5000 (LISTEN)
COMMAND    PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ControlCe 1027  you    9u  IPv4 0x6138384c7599e3e1      0t0  TCP *:7000 (LISTEN)
ControlCe 1027  you   10u  IPv6 0xa70ec8854d56944e      0t0  TCP *:7000 (LISTEN)

ControlCe is a truncated name. ps -p 1027 -o comm= shows the full path: /System/Library/CoreServices/ControlCenter.app/Contents/MacOS/ControlCenter. A request to the port gives it away too. curl -sI http://127.0.0.1:5000 answered with Server: AirTunes/960.13.1 and a 403 Forbidden.

Some guides say binding to 127.0.0.1 gets around it. On this Mac it did not. Python failed on both 0.0.0.0:5000 and 127.0.0.1:5000 with Errno 48, and Node failed with EADDRINUSE: address already in use :::5000.

Don’t kill ControlCenter. It is a system process that also runs the Control Center menu in your menu bar. You have two clean options:

  1. Use another port. For Flask that’s flask run --port 5001 (Flask itself was not installed on the test Mac). Port 5001 was free here, and a Node test server bound to it without complaint.
  2. Turn off AirPlay Receiver. Open System Settings > General > AirDrop & Handoff and switch off AirPlay Receiver. That frees 5000 and 7000, but your Mac can no longer receive AirPlay from an iPhone. I did not change this setting on the test Mac.

A reusable killport function for zsh

If you do this often, a shell function saves typing. My first version had a bug worth showing. It stored the PIDs in a plain variable and ran kill $pids. With two processes on the port (two workers sharing it via SO_REUSEPORT), zsh failed:

killport:kill:3: illegal pid: 94935\n94937

Unlike bash, zsh does not split an unquoted variable on newlines. So kill got one bogus argument. This version splits lines into an array with ${(f)...}:

killport() {
  if [[ -z $1 ]]; then
    echo "usage: killport <port> [signal]"; return 2
  fi
  local port=$1 sig=${2:-TERM}
  local -a pids
  pids=(${(f)"$(lsof -ti tcp:$port -sTCP:LISTEN)"})
  if (( ${#pids} == 0 )); then
    echo "Nothing is listening on port $port"; return 1
  fi
  lsof -nP -iTCP:$port -sTCP:LISTEN
  kill -$sig $pids && echo "Sent SIG$sig to: $pids"
}

I tested it by sourcing it from a scratch file. Against the two workers it printed both lsof lines and then:

Sent SIGTERM to: 94935 94937

Run again, it said Nothing is listening on port 8765. Use killport 8765 KILL for the -9 version. To keep it, paste the function into ~/.zshrc and open a new Terminal tab. I did not edit any shell config files for this test. If you also want to set PATH or other variables there, see how to set an environment variable permanently on Mac.

Common questions

Why does “port already in use” come back after I close Terminal?

Closing the window doesn’t always stop the process. Some servers run in the background, or were started by a tool like Docker or a launch agent. Run the lsof command again and check the COMMAND column. If it belongs to Docker, stop the container instead of killing the PID.

How do I see every listening port on my Mac?

lsof -nP -iTCP -sTCP:LISTEN

Without sudo you see your own processes, including ControlCenter.

Is netstat an option?

Yes. On this Mac, netstat -anv -p tcp | grep LISTEN printed the process name and PID at the end of each line, such as Python:95628. Watch the format, though. macOS netstat separates the port with a dot, as in 127.0.0.1.2525, so grep for .8765 rather than :8765. lsof is still easier to read.

Why does kill say “Operation not permitted”?

The process belongs to another user or to the system, and you can only signal your own processes without sudo. Check the USER column. If it’s root or starts with an underscore, it’s a system service. Find out what it is before you reach for sudo kill. (I did not try to kill anything I didn’t start.)

What about “command not found” errors while doing this?

lsof, kill and ps are built into macOS. If your dev tool itself is missing, see fixing “zsh: command not found” for brew, python and npm.