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)
| Flag | What it does |
|---|---|
-n | Show IP addresses, not hostnames (also faster) |
-P | Show port numbers, not service names |
-iTCP:8765 | Only TCP sockets on port 8765 |
-sTCP:LISTEN | Only 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:
- 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. - 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.