Skip to content
Home » lsof on Mac: Find and Kill the Process Using a Port

lsof on Mac: Find and Kill the Process Using a Port

lsof lists open files, and on macOS “open file” includes network sockets — so it answers two questions people reach for it constantly: what’s using a port you need, and what’s quietly holding a file open. The port case is the one most people arrive for, so start there. Every command below is read-only except the one kill, and all were run on macOS 26.5.2.

What’s using a port — and how to free it

A dev server won’t start because the port’s taken — “address already in use” on 3000, 8080, whatever it is. Point lsof at the port with -i:

$ lsof -i :3000 -P -n
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  60458 osxhub    4u  IPv6 0xXXXXXXXXXXXXXXXX      0t0  TCP *:3000 (LISTEN)

The USER and DEVICE columns are redacted; everything else is the real output. That’s the process holding port 3000 — its PID is 60458, and (LISTEN) confirms it’s the one accepting connections. The two flags matter: -P keeps ports numeric, because without it lsof prints the IANA service name instead — port 8000 shows as irdmi, 3306 as mysql — which is the opposite of helpful when you’re hunting a number. -n skips reverse-DNS on addresses, so the command returns immediately instead of stalling on lookups.

To free the port, you need only the PID, and -t (terse) prints exactly that — nothing else, one PID per line, made for piping:

$ lsof -ti :3000
60458

$ lsof -ti :3000 | xargs kill

lsof -ti :3000 emits the PID; xargs hands it to kill. If the port has more than one listener, -t prints every PID and xargs passes them all to a single kill, so this frees the port in one line regardless of how many processes hold it. A plain kill sends SIGTERM, which lets the process shut down cleanly; if it ignores that and the port stays occupied, lsof -ti :3000 | xargs kill -9 forces it — but -9 gives the process no chance to release resources or save state, so reach for it only after the polite version fails. Check your aim first by running the lsof -ti half alone: if it prints nothing, no process owns that port and there’s nothing to kill. For a fuller treatment of listening ports — every socket at once, and why netstat can’t print a PID on macOS — see how to list listening ports and find the process on macOS.

Space that df counts and du can’t find

lsof‘s other half is files, and it answers one that no other everyday tool does: what’s holding disk space you can’t account for. A process can keep a file open after the file is deleted. The directory entry disappears, but the data stays on disk until the last open descriptor closes — so df still counts the space while du, Finder, and find show nothing, because there’s no longer a name for them to list. On Linux, lsof tags these with (deleted) in the NAME column. macOS lsof (version 4.91) doesn’t:

$ lsof -p 37050 -a -d 3
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
Python  37050 osxhub    3r   REG   1,15 41943040 42131109 /private/tmp/lsofdemo/big.bin

That file was deleted before the command ran. lsof still prints its original path with no marker, and SIZE/OFF shows the 40 MB it’s still occupying. The one reliable signal that it’s unlinked is the link count, which +L1 adds as an NLINK column and filters to files where it’s below 1:

$ lsof -p 37050 -a +L1
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF NLINK     NODE NAME
Python  37050 osxhub    3r   REG   1,15 41943040     0 42131109 /private/tmp/lsofdemo/big.bin

NLINK 0 is the tell. To chase a disappearing-space problem across the whole system rather than one known PID, sudo lsof +L1 lists every unlinked-but-open file — but expect noise: on this machine, in an ordinary desktop session, that returned 894 rows, most of them small system caches that are unlinked by design. Sort on SIZE/OFF and look for the outlier. The space comes back when that process exits or is killed — no reboot, no rm, because the name is already gone.

Why narrowing an lsof query can widen it

lsof combines selection criteria with OR, not AND — the opposite of most command-line tools, and the reason the deleted-file query above needed -a. Adding -p PID to a search doesn’t filter it down; it adds that PID’s files to whatever you already asked for. The same two criteria, one flag apart:

lsof +L1              →  894 files   (every unlinked file on the system)
lsof +L1 -p 37050     →  901 files   (those 894 PLUS PID 37050's own descriptors)
lsof +L1 -a -p 37050  →    1 file    (only the unlinked file PID 37050 holds)

Without -a, the count goes up when you add a PID, because 894 unlinked files plus that process’s seven open descriptors is 901. With -a, it’s the intersection — the single 40 MB file this process is sitting on. Any time an lsof query returns far more than you expected, a missing -a is the first thing to check.

+d and +D scan to different depths

Two flags scan a directory for open files, and the difference is easy to get backwards. +d descends one level; +D recurses the entire tree:

$ lsof +d /private/tmp/lsofdemo/logs
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
tail    32056 osxhub    3r   REG   1,15        2 42130873 /private/tmp/lsofdemo/logs/top.txt

$ lsof +D /private/tmp/lsofdemo/logs
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
tail    32056 osxhub    3r   REG   1,15        2 42130873 /private/tmp/lsofdemo/logs/top.txt
tail    32057 osxhub    3r   REG   1,15        2 42130874 /private/tmp/lsofdemo/logs/sub/deep.txt

+d missed sub/deep.txt; +D caught it. For “what’s keeping this whole folder tree busy,” +D is the one you want, at the cost of walking every subdirectory — slow on a large tree. +d is faster when you already know the file sits directly in the named folder. One more note on scope: lsof doesn’t need sudo to list your own open files — lsof -v reports “Anyone can list all files” on this build — but it only sees other users’ and system processes’ handles when run with sudo. A port your own dev server opened is usually yours to see plainly; a system-wide +L1 sweep needs sudo to be complete.

The invocations worth keeping: what’s on a port is lsof -i :PORT -P -n, and lsof -ti :PORT | xargs kill frees it; space that df counts and du can’t find is lsof +L1 read through the NLINK column; and a query that balloons instead of narrowing is a missing -a. man lsof documents every flag here accurately — it just organizes them by flag, not by the problem that sent you looking.

Leave a Reply

Your email address will not be published. Required fields are marked *