v1.0 — Now available for macOS · Press ⌘ Space to launch
← Back to blog

Kill a process by port on macOS: every method, ranked

CmdSpace Team·

Every developer on macOS hits this at least weekly: "port 3000 is already in use." Maybe it is a Next.js dev server from a tab you closed, maybe a Docker container, maybe yesterday's experiment whose process you forgot. The fix is always t…

Every developer on macOS hits this at least weekly: "port 3000 is already in use." Maybe it is a Next.js dev server from a tab you closed, maybe a Docker container, maybe yesterday's experiment whose process you forgot. The fix is always the same — find the PID listening on the port, send it SIGTERM, move on. The methods to do that are wildly different in ergonomics.

This post ranks every credible method on macOS in 2026, from "type 40 characters" to "press one hotkey." If you skim: install a launcher with a built-in kill-by-port command and never type lsof again. If you want to know your options, read on.

The methods, ranked

1. Launcher with kill-by-port command (fastest)

Open your launcher. Type kill 3000. Done.

CmdSpace ships kill-by-port as a built-in. Raycast has a community extension. Alfred has workflows. The interaction is the same: type a port number, the launcher finds the PID, asks for confirmation, sends SIGTERM. Total elapsed time from "I need to free a port" to "port is free" is under three seconds.

# What the launcher does under the hood:
lsof -t -i :3000 | xargs kill

For developers who hit port conflicts daily, this is the only method that scales. The other methods on this list are what your launcher is doing for you.

2. kill-port npm CLI

The kill-port package is a thin Node wrapper that does the same lsof | xargs kill dance with a friendlier syntax:

npx kill-port 3000

Trade-offs: requires Node and npm. Spawns a Node process each invocation (~200 ms slower than direct shell). Output is friendlier than raw shell errors when the port has no listener.

For users who already have Node installed and do not want to memorize lsof syntax, this is reasonable.

3. lsof + kill directly

The classic. From the most common Stack Overflow answer:

lsof -i :3000
# get the PID from the output
kill -9 <PID>

Or as a one-liner:

lsof -ti :3000 | xargs kill -9

Two notes:

  • -9 (SIGKILL) is the default Stack Overflow answer; it is also too aggressive. Use plain kill (SIGTERM) first; processes get a chance to clean up. Reserve -9 for processes that ignore SIGTERM.
  • -t is "terse output" — only PIDs. Good for piping.

This is the method to know if you do not want to install anything. It is also the method you stop using once you install a launcher with kill-by-port built in.

4. netstat + ps

Older Unix muscle memory:

netstat -an | grep 3000
# note the PID column, then
ps aux | grep <pid>
kill <pid>

netstat on modern macOS does not give you PIDs directly, so this is uglier than lsof. Mostly useful if you are debugging at the socket layer and want to see all connection states (LISTEN, ESTABLISHED, TIME_WAIT) on the port.

5. Activity Monitor

For users who do not live in the terminal:

  1. Open Activity Monitor.
  2. Search for the app name.
  3. Select the process.
  4. Click the X button.

This works fine but is slower than every command-line option. Useful as a fallback when you do not know which process is listening (Activity Monitor lets you sort by network activity).

6. Docker-specific: docker compose down

If the rogue listener is a Docker container, killing the host process does not actually free the port; you need to stop the container. Common gotchas:

docker ps
docker stop <container-id>
# or
docker compose down

This is the cause of roughly half the "I killed it but the port is still busy" reports. If port-3000 conflicts persist after kill, check Docker.

7. Restart your Mac

The nuclear option. Always works. Embarrassing to admit you used it.

A practical script if you don't want a launcher

If you refuse to install a launcher with kill-by-port, the next-best ergonomics is a shell function. Add to your .zshrc:

killport() {
  if [ -z "$1" ]; then
    echo "usage: killport <port>"
    return 1
  fi
  local pids=$(lsof -ti :"$1")
  if [ -z "$pids" ]; then
    echo "nothing listening on port $1"
    return 0
  fi
  echo "killing PIDs on port $1: $pids"
  echo "$pids" | xargs kill
  sleep 1
  local stillthere=$(lsof -ti :"$1")
  if [ -n "$stillthere" ]; then
    echo "still alive, sending -9 to: $stillthere"
    echo "$stillthere" | xargs kill -9
  fi
  echo "done"
}

Usage: killport 3000. This is what a launcher's kill-by-port command does, just with manual invocation. Two-second improvement over lsof | xargs kill and you get SIGTERM-then-SIGKILL fallback for free.

Why this is a recurring problem

The structural reason "port already in use" is the most common developer error on macOS:

  1. No automatic process cleanup. When you close a terminal tab, the process running inside it sometimes lives on as an orphan, especially if it was backgrounded or forked.
  2. Dev servers are designed to be long-lived. A Next.js or Vite dev server holds the port for as long as it can, which is great for development and bad for "I forgot it was running."
  3. macOS does not show you what is using a port. Unlike Linux's ss -tulpn, macOS has no first-class "show me everything listening" command. lsof -i -P is the workaround but it is verbose.
  4. Docker desktop layers another abstraction. The host sees the port as used; the user does not always remember why.

Solving the user-facing problem (port conflict) requires a fast way to free the port. The structural problems are not getting fixed; the tooling around them is.

Common port numbers worth recognizing

Knowing which dev servers default to which ports speeds up debugging:

PortCommon owner
3000Next.js, React dev server, Express defaults
3001Secondary dev server (Payload CMS, second Next instance)
4000Phoenix, Express defaults, GraphQL
5173Vite dev server
8000Django, Python http.server
8080Tomcat, Spring Boot, common Java defaults
8888Jupyter, occasional Node tooling
5432PostgreSQL
6379Redis
27017MongoDB

If lsof -i :8080 shows nothing, the listener may be on a different port — check your tooling config.

When SIGTERM fails

Some processes ignore SIGTERM. The escalation:

  1. kill <pid> (SIGTERM, 15) — polite. Default.
  2. Wait 1-2 seconds.
  3. kill -9 <pid> (SIGKILL, 9) — forceful. Cannot be ignored.
  4. If even SIGKILL fails: the process is in uninterruptible sleep (D state in ps). Reboot.

In 99% of cases SIGTERM is enough. Of the remaining 1%, SIGKILL is enough. Of the remaining 0.01%, you have a kernel-level issue.

Process management beyond ports

Once you have kill-by-port wired into your launcher, related commands worth adding:

  • Find process by name: pgrep -f <name> then kill the PID.
  • Find process by PID: ps -p <pid> -o pid,ppid,command.
  • List all listening ports: lsof -i -P | grep LISTEN.

CmdSpace's command kit includes all four as built-ins. The lsof port cheatsheet covers the lsof commands in depth, and what is using port 3000 covers the diagnostic workflow.

The one-line answer

Install a launcher with kill-by-port. Press the hotkey, type "kill 3000," done.

For users who refuse launchers, the next-best option is a killport shell function in your .zshrc. For users who refuse shell functions, lsof -ti :3000 | xargs kill is the canonical command. Everything else on this list is fallback.

The keyboard-only Mac dev setup post covers the broader workflow this fits into. Replace Spotlight for developers explains why Spotlight is not the right tool for this category.


Sources