Processes and jobs
lsof
List open files and the processes holding them, including network sockets, deleted files and working directories.
lsof [-nP] [-a] [-i [ADDR]] [-p PID] [-u USER] [-c NAME] [+D DIR] [FILE...]
On Unix nearly everything a process uses is a file descriptor: regular files, directories,
pipes, devices and network sockets. lsof lists them, which answers a family of practical
questions: which process has this file open? (lsof FILE), what is listening on port 8080?
(lsof -i :8080), why can't I unmount this disk? (lsof +D /mnt/disk), where did my disk
space go? (lsof +L1, files deleted but still open).
Each line is one open file: COMMAND, PID, USER, FD (the descriptor number plus r, w
or u for read, write or both; or a name such as cwd, txt for the program itself, mem
for mapped libraries), TYPE (REG file, DIR, CHR device, IPv4, FIFO...), size or
offset, and NAME. Options that select (-p, -u, -c, -i, -d) are combined with OR
by default; add -a to require all of them, which is what you usually mean.
As a normal user you see only your own processes; run it as root (sudo lsof ...) to see
everyone's. Add -n and -P to skip DNS and port-name lookups, which makes it much faster and
shows numbers. For sockets alone, ss -ltnp from iproute2 is quicker and always installed.
In this sandbox the runner captures each example's output through deleted temporary files in
/var/tmp, which is why the examples filter descriptors 0 to 2 out where they would show up.
Examples
Find which process has a file open
tail -f logs/app.log > /dev/null 2>&1 &
sleep 0.2
lsof logs/app.log
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME tail 1412 dev 3r REG 0,602 729 9822732 logs/app.log
The tail holds logs/app.log open for reading (4r: descriptor 4, read mode). SIZE/OFF
is the file size and NODE its inode number.
Get only the PIDs, for kill
tail -f logs/app.log > /dev/null 2>&1 &
tail -f logs/app.log > /dev/null 2>&1 &
sleep 0.2
lsof -t logs/app.log
kill $(lsof -t logs/app.log)
wait
lsof logs/app.log || echo "nobody has it open now"
1416 1417 nobody has it open now
-t prints bare PIDs, one per line, ready for kill. Afterwards lsof finds no users and
exits 1.
Find what is listening on a port
python3 -m http.server 9000 > /dev/null 2>&1 &
sleep 0.5
lsof -i :9000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME python3 1426 dev 3u IPv4 849388373 0t0 TCP *:9000 (LISTEN)
-i :PORT selects network files on that port. *:9000 (LISTEN) means listening on all
addresses. The usual answer to "address already in use".
List all listening TCP ports
python3 -m http.server 9000 > /dev/null 2>&1 &
python3 -m http.server 9001 --bind 127.0.0.1 > /dev/null 2>&1 &
sleep 0.5
lsof -nP -i TCP -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME python3 1429 dev 3u IPv4 849451204 0t0 TCP *:9000 (LISTEN) python3 1430 dev 3u IPv4 849428973 0t0 TCP 127.0.0.1:9001 (LISTEN)
-i TCP selects TCP sockets and -sTCP:LISTEN keeps those in the listening state. The
second server binds only to loopback, shown as 127.0.0.1:9001. -nP keeps addresses and
ports numeric.
Show established connections
python3 -m http.server 9000 > /dev/null 2>&1 &
sleep 0.5
sleep 2 | nc 127.0.0.1 9000 &
sleep 0.3
lsof -nP -i :9000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME python3 1433 dev 3u IPv4 849446228 0t0 TCP *:9000 (LISTEN) python3 1433 dev 4u IPv4 849446241 0t0 TCP 127.0.0.1:9000->127.0.0.1:59050 (ESTABLISHED) nc 1436 dev 3u IPv4 849447158 0t0 TCP 127.0.0.1:59050->127.0.0.1:9000 (ESTABLISHED)
The listener, plus both ends of one connection: nc connected from a random local port to
9000, and the server's accepted socket with the same addresses reversed, each
(ESTABLISHED).
See other users' processes as root
lsof -nP -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME python3 1 root 3u IPv4 849387451 0t0 TCP 127.0.0.1:8080 (LISTEN)
The test web server runs as root, so as the dev user lsof -i :8080 would print nothing.
As root it shows the runner's python3 (PID 1) listening. On your machine, sudo lsof -i
:8080.
List the files one process has open
exec 3> out.log
tail -f logs/app.log logs/error.log > /dev/null 2>&1 &
sleep 0.2
lsof -a -p $! -d 3-99
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME tail 1441 dev 3w REG 0,602 0 9818829 /home/dev/demo/out.log tail 1441 dev 4r REG 0,602 729 9822997 /home/dev/demo/logs/app.log tail 1441 dev 5r REG 0,602 112 9822996 /home/dev/demo/logs/error.log
-p PID selects the process and -d 3-99 the descriptor numbers; -a means both must
match. The tail holds both logs, and also descriptor 3, out.log, inherited from the shell
that opened it with exec 3>. Without -d, the list would also include cwd, txt and
every mapped library (mem).
Select processes by command name
tail -f logs/app.log > /dev/null 2>&1 &
sleep 0.2
lsof -a -c tail -d cwd,3-99
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME tail 1445 dev cwd DIR 0,602 14 9823008 /home/dev/demo tail 1445 dev 3r REG 0,602 729 9823045 /home/dev/demo/logs/app.log
-c tail matches processes whose name starts with tail. The cwd line shows the
directory the process is working in.
Find out what is using a directory
tail -f logs/app.log > /dev/null 2>&1 &
(cd logs && sleep 300) &
sleep 0.2
lsof +D logs
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME tail 1449 dev 3r REG 0,602 729 9820783 logs/app.log sleep 1450 dev cwd DIR 0,602 5 9820780 logs
+D DIR looks at everything under the directory, recursively: the tail reading a file in
it and the sleep whose working directory it is. This is the check to run when umount
says target is busy. (+d checks only the top level.) The exit status is 1 because
lsof treats every file under the directory as a search item, and the other logs are open
nowhere; with +D, judge by the output, not the status.
Find deleted files that still take up space
head -c 5M /dev/zero > big.log
tail -f big.log > /dev/null 2>&1 &
sleep 0.2
rm big.log
lsof -a +L1 -p $!
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME tail 1455 dev 3r REG 0,602 5242880 0 9812258 /home/dev/demo/big.log (deleted)
Removing a file deletes its name, but the data stays on disk while any process keeps it open,
so df shows no space freed. +L1 lists open files with fewer than one link, marked
(deleted); restarting the process (or truncating via /proc/PID/fd/N) releases the space.
Machine-readable output
python3 -m http.server 9000 > /dev/null 2>&1 &
sleep 0.5
lsof -nP -F pcn -i :9000
p1460 cpython3 n*:9000
-F prints one field per line, each tagged with a letter: p PID, c command, n name.
Easier to parse reliably in scripts than the column layout.
Nothing matches
lsof -i :9999
(no output)
No output and exit status 1 when nothing matches, so lsof -i :9999 >/dev/null || echo free
works as a check that a port is unused (by processes you can see).
Common pitfalls
Forgetting
-a.lsof -u alice -ilists alice's files or any network socket;lsof -a -u alice -ilists alice's sockets.Running it without root and concluding "nothing has the port open". Other users' processes, including system services, are invisible to a normal user.
Waiting for slow output. Without
-nand-P,lsofresolves every address and port name, and it may stall on unreachable network mounts; use-nP, and-bto avoid blocking kernel calls.Using
+Don a huge tree. It examines every file below the directory and can take a long time; narrow it down or uselsof /mnt/pointfor a mount point.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
lsofis not part of a minimal Linux install (Debian packagelsof) but is preinstalled on macOS, where the same options work.fuser(psmisc) is a smaller Linux tool for "who uses this file or port":fuser -v 9000/tcp.On Linux,
ss -ltnpandss -tnplist listening and established sockets with their processes faster thanlsof -i; macOS has noss, solsof -nP -iTCP -sTCP:LISTENis the usual idiom there.
Options worth knowing
A selection, described in our own words. For the full list, see lsof(8) or lsof --help.
| Option | What it does |
|---|---|
-i[=ADDR] | Select network files, optionally matching [46][proto][@host][:port], e.g. -i :80 or -i TCP@10.0.0.5. |
-n | Do not resolve IP addresses to host names. |
-P | Do not convert port numbers to service names. |
-a | Combine the selection options with AND instead of OR. |
-p PIDS | Select processes by PID (comma-separated; ^PID excludes). |
-u USERS | Select processes by user name or UID (^USER excludes). |
-c NAME | Select processes whose command name starts with NAME. |
-d FDS | Select file descriptors, e.g. 0-2, cwd, txt, or ^mem to exclude. |
-t | Print only PIDs, one per line. |
-s PROTO:STATE | With -i, filter by protocol state, e.g. -sTCP:LISTEN. |
+D DIR | List open files anywhere below DIR (recursive). |
+d DIR | List open files directly in DIR (not recursive). |
+L1 | List open files with no remaining links, i.e. deleted but still open. |
-F FIELDS | Print fields one per line with letter tags, for parsing. |
-r SECONDS | Repeat the listing every SECONDS until interrupted. |
-b | Avoid kernel calls that might block (for hung network file systems). |
-w | Suppress warning messages. |
-R | Add a PPID (parent PID) column. |
-U | Select Unix domain socket files. |
Related
psPrint a snapshot of running processes, with the columns, filters and sort order you choose.killSend a signal to processes by PID or job number; by default TERM, which asks a process to exit.pgrepPrint the PIDs of processes whose name (or full command line) matches a pattern, with filters by user, parent and age.ssList network sockets on Linux; above all, which ports are listening and which connections are open.dfReport how much space is used and free on mounted filesystems.