lsof Command: Tutorial & Examples

List open files — and on Unix, everything is a file.

What It Is

lsof stands for list open files, and the trick to loving it is taking that name absolutely literally. On Unix, everything is a file — a process talking to disk has a file open, a process listening on port 8080 has a file open, a process reading from your keyboard has a file open, a process talking to a database over the network has a file open. So lsof doesn't just list files in the desktop sense — it lists every open network connection, every listening port, every pipe between two programs, every memory-mapped library, every device node, every deleted-but-still-held log file on the entire system. One uniform table.

If you've never touched a server before, this is the command you'll fall in love with about a month in — the first time something says "Address already in use" and you can't figure out what's holding port 8080, or the first time df says the disk is 100% full but du only finds half that much. Both of those are lsof questions with one-line answers, and we'll get to them. Along the way we'll explain every column, learn the small handful of flags that do 90% of real work, and pick up the "everything is a file" idea that runs through all of Unix.

Your First Look

The killer one-liner, the one most admins discover six months too late:

lsof -i :22
COMMAND   PID  USER FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd      892  root 3u   IPv4  18402      0t0  TCP *:ssh (LISTEN)
sshd    14021  root 4u   IPv4  48522    0t0   TCP server:ssh->client:52758 (ESTABLISHED)
ssh   2397752 arndt 3u   IPv4  48522    0t0   TCP xps:52758->server-01:ssh (ESTABLISHED)

Three lines, and they answer the question every newcomer asks: what's on port 22, and who's connected to it? Top row: the sshd daemon is listening (*:ssh (LISTEN)). Middle row: someone is currently connected to it. Bottom row: my outgoing ssh session that initiated the connection. One command, full picture — process name, PID, user, protocol, endpoints, state.

The bare command — lsof with no arguments — dumps every open file on the whole system, which on a busy box is tens of thousands of rows and not what you want. You almost always run lsof with one filter: a port, a PID, a path, or a user. That's the shape of every real session.

How I Use It

There are about four lsof questions I find myself asking on real servers, and they cover almost everything.

"What's listening on this port?" This is the most common, by a wide margin. lsof -i :8080 and you instantly know which process is holding the port — usually the answer to "I can't restart my server, address already in use." The -i flag selects internet sockets (TCP and UDP), and the :PORT syntax narrows to one port; lsof -i tcp:8080 adds the protocol; lsof -i @1.2.3.4 filters by remote host. This one flag is why lsof ended up installed on every server on earth.

"What is this process actually touching?" lsof -p PID lists every file (sockets, pipes, libraries, regular files, devices — all of it) that one process has open. Read top-to-bottom and you see the whole shape of a program: which config files it loaded at startup (mem rows), which log files it's writing now (REG with a path under /var/log), which databases it's talking to (TCP rows), which directory it's running in (cwd). Pair with strace when you need to see what it's doing right now: lsof shows state, strace shows behavior. They're complements.

"Who's locking this file/directory and why can't I unmount it?" lsof /path/to/file lists every process with that file open. lsof +D /mnt/data does the same for everything under a directory (it's slow — it walks the whole tree — hence the *SLOW?* warning in lsof -h). The classic case is umount refusing with "target is busy"lsof +D /mnt names the culprit in two seconds. (fuser is the older, terser cousin that does the same job in fewer characters; both work.)

"Where did my disk space go?" The one that feels like magic the first time: lsof | grep deleted. See the Gotcha — it's the trick that recovers your full disk.

Pro Tip

lsof -i :PORT is the answer to about 80% of "what's holding this port?" questions you'll ever ask. Memorize it before any other lsof invocation. Add -P (don't translate port numbers to service names — show :8080, not :http-alt) and -n (don't reverse-DNS the IPs — much faster, no hangs on stale DNS) and you get a snappy, unambiguous answer every time: lsof -i :8080 -Pn.

The Columns Explained

Every column in default output, in order. Once you know these, every row reads like a sentence.

  • COMMAND — the program holding the file (first 9 chars by default; widen with +c 0 for the full name). Same name you'd see in ps's COMMAND column.
  • PID — the process ID, the number you feed to kill once you've found the culprit.
  • USER — who owns the process. A process running as root holding files it shouldn't is a security smell.
  • FD — the file descriptor: usually an integer (0, 1, 2, 3, …), sometimes a name. The integer is the slot number the kernel handed the process when it opened the file — 0/1/2 are always stdin/stdout/stderr, the rest are everything else the program opened in order. A trailing letter is the access mode: r read, w write, u read-write. Named slots are special: cwd current working directory, rtd root directory, txt the program binary itself, mem a memory-mapped file (usually a shared library — you'll see a wall of these per process, it's normal), DEL a file that's been deleted but still mapped.
  • TYPE — what kind of file. The vocabulary here is the whole "everything is a file" lesson made visible: REG a regular file on disk, DIR a directory, CHR a character device (like /dev/tty), BLK a block device (a disk), FIFO a pipe, unix a Unix-domain socket, IPv4/IPv6 a network socket, a_inode an anonymous inode (eventfd, signalfd, the things epoll is built on). Whatever the kernel hands out a file descriptor for has a type here.
  • DEVICE — the device numbers (major,minor) of the underlying filesystem, or the kernel's address for sockets. Mostly noise; useful for distinguishing two filesystems with the same path.
  • SIZE/OFF — file size in bytes, or the current read/write offset for things being actively streamed (e.g. 0t52428800 = byte offset 52428800 — that's what 0t means, decimal offset).
  • NODE — the inode number for regular files; the protocol (TCP, UDP) for sockets; PIPE for pipes. Surprisingly diagnostic — two paths sharing one inode are hard-linked.
  • NAME — what it actually is. For regular files: the full path. For sockets: local-addr:port->remote-addr:port (STATE), e.g. *:ssh (LISTEN), xps:52758->server-01:ssh (ESTABLISHED). For pipes: pipe plus an ID. For deleted files: the original path with (deleted) appended — the most useful four characters in this whole command.

Reading It by Example

The handful of lsof recipes that turn into muscle memory.

Who's holding port 8080?

lsof -i :8080 -Pn

The single most common reason to run lsof. -Pn keeps it fast and shows numbers (no DNS lookups, no /etc/services translation). ss -ltnp is the modern alternative — same answer, different tool family.

What's every listening port on this box?

lsof -i -P -n | grep LISTEN

The fast audit before you trust a fresh server. Every row is something the world can reach. Anything unexpected (a MySQL bound to *:3306 instead of 127.0.0.1:3306?) is a finding.

Every file one program has open:

lsof -p 2323

Or by name, no PID lookup needed:

lsof -c nginx

-c matches the process command (substring), so this shows files held by every nginx worker at once.

Which process is locking this file/mount?

lsof /var/log/app.log
lsof +D /mnt/data

The first is instant; the second walks the whole tree (slow but thorough) and is what unblocks a stuck umount.

The deleted-files trick — the one that recovers a full disk:

lsof | grep deleted

If df says the disk is full but du can't find the data, this is almost always why. See the Gotcha — it deserves its own callout.

Everything owned by one user:

lsof -u www-data

Pair with -i to narrow to that user's sockets: lsof -u www-data -i. Great for "what is this service actually doing?"

Cheat Sheet

The flags worth memorizing — in roughly the order you'll need them:

  • lsof -i :PORT — what's on this port? (Add -Pn for speed and clarity.)
  • lsof -i — every network connection on the box.
  • lsof -i tcp / -i udp / -i @host — narrow by protocol or remote host.
  • lsof -p PID — every file one process has open.
  • lsof -c name — every file every process with that command name has open.
  • lsof /path/to/file — who's got this file open.
  • lsof +D /path — who's got anything under this directory open (slow — walks the tree).
  • lsof -u user — files held by one user (or -u ^root to exclude).
  • lsof | grep deleted — the disk-full rescue trick.
  • lsof -P — show port numbers, not service names.
  • lsof -n — no DNS reverse lookups (much faster, no hangs).
  • lsof -t — terse: PIDs only, perfect for kill $(lsof -t /var/log/app.log).
  • lsof -a — combine filters with AND instead of the default OR (e.g. lsof -a -u alice -i = alice's network connections, not "alice's files OR all network connections").
  • lsof -r 5 — repeat every 5 seconds; turns lsof into a live monitor.

How You'll Actually Use It

In real life lsof lives in three moments. The port detective: something says "address in use," you type lsof -i :PORT, you find the culprit, you kill it (or, better, restart its systemd unit). The unmount unblocker: umount /mnt refuses with device busy, you run lsof +D /mnt, you find the shell someone left cd'd into the mount point, you wave at them in Slack. The disk-space rescue: df shows 100% full, du can't account for it, lsof | grep deleted reveals the log file someone rm'd while it was still being written.

Outside those, it's a reference tool. When strace shows you a process hammering file descriptor 17 and you need to know what file is FD 17, lsof -p PID is the answer. When htop shows you a process and you press l, htop is just launching lsof -p for you — same data, prettier launcher.

Gotchas

  • The "deleted but still held open" trick — and why it matters. When a program has a file open and someone rms the file, the directory entry vanishes but the file stays on disk — the kernel only frees the blocks when the last open file descriptor closes. So a service writing a 50 GB log, someone rms the log to "free space," df still shows the disk full, du can't find a thing. lsof | grep deleted names the culprit. The fix isn't another rm — there's nothing left to remove. You either restart the process (closes the FD, kernel reclaims the blocks) or, the elegant trick, truncate the file via its /proc entry: : > /proc/PID/fd/17 (where 17 is the FD column). This is the lsof skill that pays for the whole command.
  • Without root, you mostly see your own files. lsof reads through /proc, and /proc/<PID>/fd is only readable by the process's owner (or root). Run it without sudo and you see every process's name but only the open-file details for processes you own. For real diagnosis: sudo lsof. (On hardened boxes with hidepid mounted on /proc, even less.)
  • lsof is slow on big boxes, and warning-noisy. Each row is a /proc read; tens of thousands of files means seconds of wall time. The +D recursive scan is much slower (it stats every file). Expect harmless WARNING: can't stat() lines for sysfs and per-service systemd-private-* tmpfs mounts you don't have permission to enter — they're noise from the tool being thorough, not real errors.
  • The default is OR, not AND. lsof -u alice -i lists "alice's files OR all network sockets" (i.e. everything with a network socket plus alice's local files). To narrow with AND you must pass -a: lsof -a -u alice -i. Counterintuitive and bites everyone once.
  • -c NAME matches the truncated command name (first 15 characters, same kernel limit that bites pgrep). Long binary names need partial matches or lsof -p $(pgrep -f longname).
  • A PID you saved earlier may now be a different program. Same trap as ps and kill — re-check before you act.

History & Philosophy

lsof was written by Victor A. Abell at Purdue University starting in 1991, and maintained by him for over twenty-five years across dozens of Unix variants (Solaris, AIX, HP-UX, Irix, the BSDs, Linux) — each with a completely different way of exposing open-file state in the kernel. That portability work is why the lsof flag set is so dense: every flag had to do roughly the same thing on every Unix on earth. The project now lives at lsof-org/lsof on GitHub, carried by a small team after Vic retired.

The deeper magic is the one that runs through top, ps, pgrep, and htop: lsof isn't doing anything you can't do yourself. It walks /proc, reads each /proc/<PID>/fd/ directory (every open file descriptor is a symlink there), stats the target, and prints it. That's it. But what it demonstrates by lining them all up — that a TCP connection, a directory, a deleted log file, and a shared library all appear in the same table with the same columns — is the cleanest possible illustration of Unix's central design idea: everything is a file. A socket is a file. A device is a file. A running program's memory is a file. The kernel hands out a small integer for each one and you read and write through it with the same read() and write() calls. lsof is the table view of that universe, and once you've stared at it for a while the box stops being mysterious.

See Also

  • ss — the modern socket inspector; faster than lsof -i for pure network questions
  • netstat — the older socket tool ss replaced; you'll still see it everywhere
  • fuser — terser cousin for "who has this file open?"
  • ps / top / htop — find the PID before you lsof -p it
  • stracelsof shows state, strace shows behavior; they're complements
  • kill — what you do once lsof names the culprit
  • df / du — the disk-full pair, completed by lsof | grep deleted
  • pgrep — find PIDs by name; pair with lsof -p $(pgrep ...)
  • umount — the command that fails with "device busy" until lsof +D rescues you
  • /proc — where every row in lsof actually comes from
  • process / file / pipe — the concepts behind every column
  • disk full / device busy — the two problems lsof solves the fastest

Got an "address already in use" or a disk that's full of nothing?

CleverUptime tracks every listening port and every filesystem on every server, and when one fills up or a port goes silent it tells you in plain language — and points at the exact lsof one-liner that names the culprit, so you don't have to dig.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →