opscape

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...]

12 of 12 examples run in Debian 13.7 on 2026-10-31 · manual: lsof(8)

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
output
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"
output
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
output
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
output
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
output
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
explain
output
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
output
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
output
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
outputexit 1
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 $!
output
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
output
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
explain
outputexit 1

(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 -i lists alice's files or any network socket; lsof -a -u alice -i lists 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 -n and -P, lsof resolves every address and port name, and it may stall on unreachable network mounts; use -nP, and -b to avoid blocking kernel calls.

  • Using +D on a huge tree. It examines every file below the directory and can take a long time; narrow it down or use lsof /mnt/point for 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).

  • lsof is not part of a minimal Linux install (Debian package lsof) 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 -ltnp and ss -tnp list listening and established sockets with their processes faster than lsof -i; macOS has no ss, so lsof -nP -iTCP -sTCP:LISTEN is the usual idiom there.

Options worth knowing

A selection, described in our own words. For the full list, see lsof(8) or lsof --help.

OptionWhat it does
-i[=ADDR]Select network files, optionally matching [46][proto][@host][:port], e.g. -i :80 or -i TCP@10.0.0.5.
-nDo not resolve IP addresses to host names.
-PDo not convert port numbers to service names.
-aCombine the selection options with AND instead of OR.
-p PIDSSelect processes by PID (comma-separated; ^PID excludes).
-u USERSSelect processes by user name or UID (^USER excludes).
-c NAMESelect processes whose command name starts with NAME.
-d FDSSelect file descriptors, e.g. 0-2, cwd, txt, or ^mem to exclude.
-tPrint only PIDs, one per line.
-s PROTO:STATEWith -i, filter by protocol state, e.g. -sTCP:LISTEN.
+D DIRList open files anywhere below DIR (recursive).
+d DIRList open files directly in DIR (not recursive).
+L1List open files with no remaining links, i.e. deleted but still open.
-F FIELDSPrint fields one per line with letter tags, for parsing.
-r SECONDSRepeat the listing every SECONDS until interrupted.
-bAvoid kernel calls that might block (for hung network file systems).
-wSuppress warning messages.
-RAdd a PPID (parent PID) column.
-USelect Unix domain socket files.

Related