DEV Community

Cover image for How to Find Which Project Is Running on a Port on Mac
Anmol Sethi
Anmol Sethi

Posted on Originally published at Medium

How to Find Which Project Is Running on a Port on Mac

You open localhost:3000 and it's the wrong app. Or npm run dev says the port is taken, and lsof tells you it's node. Every dev server is node.

What you actually need is the folder it was started from. macOS doesn't have Linux's pwdx, but lsof can tell you, and it takes about ten seconds.

The quick answer

Swap 3000 for your port:

lsof -a -p "$(lsof -ti tcp:3000 -sTCP:LISTEN | head -1)" -d cwd -Fn | sed -n 's/^n//p'
Enter fullscreen mode Exit fullscreen mode

It prints the working directory of whatever is listening on the port, like /Users/you/Code/acme-web. The rest of this post is what each part does, how to get the branch and the command too, and what to do when the answer is just /.

Step 1: find the PID

lsof -nP -iTCP:3000 -sTCP:LISTEN
Enter fullscreen mode Exit fullscreen mode
COMMAND   PID  USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    48211   you   23u  IPv6  0x7e9…      0t0  TCP *:3000 (LISTEN)
Enter fullscreen mode Exit fullscreen mode

-n and -P skip the DNS and port-name lookups, so it answers instantly and shows 3000 instead of hbci. -sTCP:LISTEN keeps only the server. Without it you also get every client connected to the port, including your browser tab.

Add -t when you only want the number: lsof -ti tcp:3000 -sTCP:LISTEN prints 48211.

Step 2: ask lsof for its working directory

Every process has a current directory, and lsof lists it as a file descriptor called cwd:

lsof -a -p 48211 -d cwd
Enter fullscreen mode Exit fullscreen mode
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF      NODE NAME
node    48211  you  cwd    DIR   1,18      640 382905693 /Users/you/Code/acme-web
Enter fullscreen mode Exit fullscreen mode

-p picks the process, -d cwd picks the descriptor, and -a means both must match. Leave out -a and lsof ORs them, so you get every process's cwd on the machine.

For scripts, -Fn prints just the path with an n in front, which sed -n 's/^n//p' strips off. That's the quick answer above.

For most dev servers this is the project folder, because npm run dev, pnpm dev, rails s and go run . all run where you typed them.

Step 3: see the full command

ps -o command= -p 48211
Enter fullscreen mode Exit fullscreen mode

This shows the whole command line, which often holds the path as well: node /Users/you/Code/acme-web/node_modules/.bin/vite. It also tells two servers in the same folder apart, like vite and storybook.

To see when it started, which helps spot the one you left running yesterday:

ps -o lstart= -p 48211
Enter fullscreen mode Exit fullscreen mode

Step 4: get the git branch

git -C /Users/you/Code/acme-web branch --show-current
Enter fullscreen mode Exit fullscreen mode

-C runs git as if you were in that folder, so you don't have to cd. This matters more than it used to. If you keep several worktrees of one repo, or a coding agent runs servers in its own worktree, you can have three copies of the same app on three ports. The folder and the branch are the only things that tell them apart.

Two more that help here:

  • git -C <folder> rev-parse --show-toplevel gives the repo root when the server runs in a subfolder, like packages/web in a monorepo.
  • git -C <folder> worktree list shows every worktree of that repo and its branch.

Make it one command

Add this to ~/.zshrc, open a new terminal, and run whoport 3000:

whoport() {
  local pid dir
  pid=$(lsof -ti tcp:"$1" -sTCP:LISTEN | head -1)
  [[ -z $pid ]] && { echo "Nothing is listening on :$1"; return 1; }
  dir=$(lsof -a -p "$pid" -d cwd -Fn | sed -n 's/^n//p')
  echo "PID     $pid"
  echo "Command $(ps -o command= -p "$pid")"
  echo "Folder  $dir"
  git -C "$dir" rev-parse --is-inside-work-tree &>/dev/null &&
    echo "Branch  $(git -C "$dir" branch --show-current)"
}
Enter fullscreen mode Exit fullscreen mode
% whoport 3000
PID     48211
Command node /Users/you/Code/acme-web/node_modules/.bin/vite
Folder  /Users/you/Code/acme-web
Branch  feature/search
Enter fullscreen mode Exit fullscreen mode

To see every listening server with its folder at once:

lsof -tiTCP -sTCP:LISTEN | sort -u | while read pid; do
  printf '%-6s %-18.18s %s\n' "$pid" "$(ps -o comm= -p "$pid" | sed 's#.*/##')" \
    "$(lsof -a -p "$pid" -d cwd -Fn | sed -n 's/^n//p')"
done
Enter fullscreen mode Exit fullscreen mode

You'll see a few apps of your own in there too, like your editor's helpers. Their folder is usually /.

If you check this more than once a day: full disclosure, I built Portlivo, a Mac menu bar app that shows every dev server with its project, git branch and port. Press E on one and its folder opens in VS Code, Cursor or Zed. It's $8.99 once for the first 50 licences, after a 7-day free trial. The commands above are free and enough if you only need this now and then.

Open it in your editor

Once you have the folder, one command opens it:

Editor Command Install the command
VS Code code ~/Code/acme-web Command Palette → Shell Command: Install 'code' command in PATH
Cursor cursor ~/Code/acme-web Command Palette → Shell Command: Install 'cursor' command
Zed zed ~/Code/acme-web Zed menu → Install CLI
Any app open -a "Visual Studio Code" ~/Code/acme-web Nothing to install

Put them together and you jump from a port straight to its code:

code "$(lsof -a -p "$(lsof -ti tcp:3000 -sTCP:LISTEN | head -1)" -d cwd -Fn | sed -n 's/^n//p')"
Enter fullscreen mode Exit fullscreen mode

When the folder is / or makes no sense

Some servers aren't started from a project folder. lsof is still right; the folder just isn't the useful answer.

What you see What it is Where to look instead
com.docker.backend or OrbStack holds the port A container docker ps --filter publish=3000, then the Compose label below
Folder is /, command is postgres, redis-server or mysqld A Homebrew service, started by launchd brew services list. It isn't tied to a project.
Folder is /, command is nginx Laravel Herd or Valet herd links or valet links list the folder behind each .test site
Folder ends in /public php artisan serve, which runs from the public folder git -C <folder> rev-parse --show-toplevel for the project root
Folder is your home folder or /, process is node Started by pm2 or another process manager pm2 list, then pm2 describe <name> shows its exec cwd
lsof prints nothing, but the port is taken A process owned by root or another user Run the same commands with sudo

For a Docker Compose container, this prints the folder of the project that started it:

docker inspect <container> | grep working_dir
Enter fullscreen mode Exit fullscreen mode
"com.docker.compose.project.working_dir": "/Users/you/Code/acme-web",
Enter fullscreen mode Exit fullscreen mode

If the port is taken and you want it back, I wrote up how to stop the process on a port safely, including the ones that come straight back.

FAQ

Is there a pwdx command on Mac?
No. pwdx and /proc/<PID>/cwd are Linux only. On macOS, lsof -a -p <PID> -d cwd does the same job.

How do I find which port a project is using?
Run the loop from "Make it one command", which lists every listening server with its folder, and look for yours. Or check the project's config: Vite defaults to 5173, Next.js and Rails to 3000, Laravel to 8000.

Does this work for Python, Ruby, Go and PHP servers?
Yes. lsof and ps don't care what language the process is in. I tested the steps above with python3 -m http.server.

Why does lsof show nothing for a port I know is in use?
Usually the process belongs to another user, so try sudo. If it's a Docker container, lsof shows Docker's own process, not the app inside it.

Can I see this without the terminal?
Activity Monitor shows a process's open files and ports (double-click it, then Open Files and Ports), and the cwd is in that list. It's slower for this than lsof, but it works.

The bottom line

lsof finds the PID, lsof -d cwd turns it into a folder, and git -C gives you the branch. Put them in a whoport function and "which project is this?" takes one command.

How many dev servers do you usually have running at once?

I'm Anmol. I build Mac apps and write about Mac productivity. Follow me on X at @anmolsethi_dev.

Top comments (0)