Portis

lsof Cheat Sheet for Mac: Ports, PIDs, Listeners and Gotchas

Published
Tested on
macOS 27.2
Author
Christoph Schröder

To list every listening TCP port on a Mac with the process behind it, run:

lsof -nP -iTCP -sTCP:LISTEN

For one port, use lsof -nP -iTCP:3000 -sTCP:LISTEN. For just the PID, use lsof -ti tcp:3000 -sTCP:LISTEN. Everything here is for the lsof that ships with macOS (4.91 on macOS 27.2), which differs from the Linux one.

lsof commands for ports on a Mac

Task Command
All listening TCP ports lsof -nP -iTCP -sTCP:LISTEN
One port, listener only lsof -nP -iTCP:3000 -sTCP:LISTEN
Several ports lsof -nP -iTCP:3000,5173 -sTCP:LISTEN
A range of ports lsof -nP -iTCP:3000-3010 -sTCP:LISTEN
Just the PID lsof -ti tcp:3000 -sTCP:LISTEN
Every socket on a port, clients included lsof -nP -i :3000
UDP sockets lsof -nP -iUDP
Listeners of one program lsof -nP -a -c node -iTCP -sTCP:LISTEN
Sockets of one PID lsof -nP -a -p 3597 -i
Working directory of a PID lsof -a -p 3597 -d cwd
Full command line of a PID ps -o command= -p 3597
Full process names lsof +c 0 -nP -iTCP -sTCP:LISTEN
Wait until a port is free lsof -nP -iTCP:3000 -sTCP:LISTEN +r 1 >/dev/null
Every user's listeners sudo lsof -nP -iTCP -sTCP:LISTEN

Pass -n and -P every time. -n skips reverse DNS lookups, which on a cold run is the difference between about a second and 0.05 seconds. -P keeps port numbers. Without it, port 3000 prints as *:hbci and 8080 as *:http-alt, and grep ':3000' finds nothing.

If you come from Linux, the first row is the Mac's ss -tlnp. netstat -tulpn doesn't translate, because on macOS netstat's -p takes a protocol, not a process.

How to read lsof output

lsof -nP -iTCP -sTCP:LISTEN
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
rapportd  716 alex   11u  IPv4 0xa4b9aa9c966eb26d      0t0  TCP *:49178 (LISTEN)
Raycast  2286 alex   40u  IPv4 0x28c2ed5af4c205c3      0t0  TCP 127.0.0.1:7265 (LISTEN)
node     3597 alex   17u  IPv6 0x8cc6e94f298c8068      0t0  TCP *:3000 (LISTEN)
node     3944 alex   14u  IPv4 0x98eab4974281a3a0      0t0  TCP 127.0.0.1:9229 (LISTEN)

The columns to read are COMMAND, PID, USER and NAME. COMMAND is cut to 9 characters, and +c 0 prints it in full. Names you don't recognize, like rapportd, are mostly Apple's: see Apple processes listening on a Mac.

In NAME, *:3000 means every interface, so other machines on your network can reach the port unless a firewall stops them. 127.0.0.1:9229 or [::1]:5173 is loopback, reachable only from this Mac.

An IPv6 line with *:3000 is usually a dual-stack socket that accepts IPv4 too. Node's server.listen(3000) opens one, so 127.0.0.1:3000 still connects.

Find which process is using a port

Add -sTCP:LISTEN. Without it, lsof also returns every connection to the port, the client side included. Here nc is connected to a Node server:

lsof -nP -i :3000
COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    3597 alex   17u  IPv6 0x8cc6e94f298c8068      0t0  TCP *:3000 (LISTEN)
node    3597 alex   21u  IPv6 0x68920d4f7fad4691      0t0  TCP 127.0.0.1:3000->127.0.0.1:53024 (ESTABLISHED)
nc      4012 alex    3u  IPv4  0xb55d6ed30db143e      0t0  TCP 127.0.0.1:53024->127.0.0.1:3000 (ESTABLISHED)

Here lsof -ti :3000 prints two PIDs. Usually the second one is your browser, and kill -9 $(lsof -ti :3000) can take the browser's helper process down with the server. With the filter you get only the server:

lsof -ti tcp:3000 -sTCP:LISTEN
3597

Write tcp:3000, not :3000. UDP sockets have no state, so -sTCP:LISTEN doesn't filter them out.

-t prints bare PIDs, and lsof exits with 1 when nothing matches, which makes a quick check:

lsof -ti tcp:3000 -sTCP:LISTEN >/dev/null && echo "port 3000 is taken"

To end the process safely, see how to kill the process on a port.

Filter by process name or PID

lsof -nP -a -c node -iTCP -sTCP:LISTEN

lsof ORs its selection options. Without -a, this asks for every file any node process has open, plus every listening TCP socket on the system, and prints hundreds of lines. With -a you get node's listeners only. The same goes for -p.

-c node matches any name that starts with node. For an exact match, use -c '/^node$/'.

Find the folder and command line behind a PID

Three node rows look the same until you know where each one runs. lsof has the working directory. For the arguments you need ps, because lsof never shows them:

lsof -a -p 3597 -d cwd -Fn | sed -n 's/^n//p'
ps -o command= -p 3597
/Users/alex/code/shop-api
node /Users/alex/code/shop-api/server.js --port 3000

-Fn switches lsof to one field per line, and sed keeps the path.

Why lsof shows nothing for a port that is in use

The process probably belongs to root or another user. Without sudo, lsof lists only your own processes, and on macOS it always has. A caddy that runs as root on port 443 doesn't show up. With sudo it lists everyone's:

sudo lsof -nP -iTCP -sTCP:LISTEN

netstat used to be the way to look without sudo. On macOS 27 it can print no ports at all, and nettop -L 1 -m tcp -n is what still shows every user's sockets.

Portis puts these lookups in one table. Type 3000 or a folder name like shop-api in the search field, and the matching row shows the PID, user, address, working directory and full command line. Root's listeners are in the list without sudo.

Portis lists every open port on your Mac and the process behind it.

It is a Mac app that also shows whether the network can reach each port, and it can end your own processes. Everything is read locally and nothing is sent anywhere.

Download on the Mac App Store Download on the Mac App Store

$9.99, once. macOS 14 or later.
No account, no tracking.