Linux lsof: List Open Files

The `lsof` command (list open files) is an indispensable diagnostic tool for anyone managing Linux systems. At its core, lsof does exactly what its name suggests: it lists all files currently open on...

Key Insights

  • lsof reveals all open files on a Linux system, including regular files, network sockets, pipes, and devices—essential for debugging resource locks and port conflicts
  • The tool excels at network troubleshooting, instantly showing which process is using a specific port with lsof -i :PORT
  • Combining filters with AND (-a) and OR (comma-separated) operations makes lsof powerful for complex queries across users, processes, and file types

Introduction to lsof

The lsof command (list open files) is an indispensable diagnostic tool for anyone managing Linux systems. At its core, lsof does exactly what its name suggests: it lists all files currently open on your system. But in the Unix philosophy where “everything is a file,” this becomes far more powerful than it initially sounds.

In Linux, regular files are just the beginning. Network sockets, pipes, directories, device files, and even deleted files still held open by processes—all of these are “files” that lsof can track. This makes lsof your go-to tool for answering questions like “why can’t I unmount this filesystem?”, “what process is listening on port 8080?”, or “why is my disk full when du shows plenty of space?”

Here’s what basic lsof output looks like:

$ lsof | head -10
COMMAND    PID   USER   FD      TYPE             DEVICE  SIZE/OFF       NODE NAME
systemd      1   root  cwd       DIR              253,0      4096          2 /
systemd      1   root  rtd       DIR              253,0      4096          2 /
systemd      1   root  txt       REG              253,0   1632776    1048698 /usr/lib/systemd/systemd
systemd      1   root  mem       REG              253,0     51856    1048833 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
nginx     1234   www   4u      IPv4              12345       0t0        TCP *:80 (LISTEN)
nginx     1234   www   5u      IPv4              12346       0t0        TCP 192.168.1.10:80->192.168.1.20:54321 (ESTABLISHED)
postgres  5678   db    3u      unix 0xffff880123456789       0t0      23456 /var/run/postgresql/.s.PGSQL.5432

Each line shows the command name, process ID, user, file descriptor, file type, and the actual file or connection. This rich output is what makes lsof so versatile.

Basic Usage and Syntax

Running lsof without arguments dumps every open file on the system—typically thousands of entries. This is rarely what you want. Instead, you’ll filter by file, user, or process.

To see which processes have a specific file open:

$ lsof /var/log/nginx/access.log
COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
nginx   1234 www-data    4w   REG  253,0  1048576 1234567 /var/log/nginx/access.log
nginx   1235 www-data    4w   REG  253,0  1048576 1234567 /var/log/nginx/access.log

This is invaluable when you encounter “device or resource busy” errors. You can immediately see what’s holding the file open.

To recursively check all files in a directory:

$ lsof +D /var/lib/mysql

Warning: +D performs a full directory tree walk and can be slow on large directories. Use it judiciously.

To see all files opened by a specific user:

$ lsof -u postgres

This shows everything a user has open—useful for auditing or understanding what a compromised account might be accessing. You can also exclude a user with ^:

$ lsof -u ^root  # Everything except root's files

Network Connections and Ports

This is where lsof truly shines. Network troubleshooting becomes trivial when you can instantly map ports to processes.

List all network connections:

$ lsof -i
COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
sshd      890 root    3u  IPv4   12345      0t0  TCP *:22 (LISTEN)
nginx    1234 www     4u  IPv4   12346      0t0  TCP *:80 (LISTEN)
postgres 5678 db      5u  IPv4   12347      0t0  TCP localhost:5432 (LISTEN)
chrome   9999 user   42u  IPv4   12348      0t0  TCP 192.168.1.10:54321->93.184.216.34:443 (ESTABLISHED)

Find what’s using a specific port—the most common use case:

$ lsof -i :8080
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    2345 dev    12u  IPv4  23456      0t0  TCP *:8080 (LISTEN)

This immediately tells you which process is hogging that port. No more guessing.

Filter by protocol:

$ lsof -i TCP
$ lsof -i UDP
$ lsof -i TCP:1-1024  # Privileged ports only

Check connections to a specific host:

$ lsof -i @93.184.216.34

You can combine port and protocol:

$ lsof -i TCP:443  # All HTTPS connections

Process-Specific Queries

When debugging a specific process, you want to see everything it has open.

Query by process ID:

$ lsof -p 1234

This shows all file descriptors for that PID—regular files, sockets, pipes, everything.

Query by process name:

$ lsof -c nginx

The -c flag matches command names. It accepts partial matches, so lsof -c ssh catches both sshd and ssh.

For scripting, you often just need the PID:

$ lsof -t -i :8080
2345

The -t flag outputs PIDs only, perfect for piping to other commands:

$ kill $(lsof -t -i :8080)

This one-liner kills whatever’s on port 8080. Use with caution.

Advanced Filtering and Combination Options

The real power emerges when combining filters. By default, lsof uses OR logic—it shows results matching any condition. Use -a to require AND logic.

Show network connections for a specific user:

$ lsof -a -u www-data -i

Without -a, this would show all www-data files OR all network files. With -a, you get only network files owned by www-data.

Show files opened by a specific user AND command:

$ lsof -a -u postgres -c postgres

Multiple values for the same option use OR:

$ lsof -u user1,user2  # Files by user1 OR user2
$ lsof -i :80,:443     # Processes on port 80 OR 443

Exclude with ^:

$ lsof -i -u ^root  # Network connections not owned by root

Practical Troubleshooting Scenarios

Scenario 1: Can’t delete or unmount a file/directory

$ rm large_file.dat
rm: cannot remove 'large_file.dat': Device or resource busy

$ lsof large_file.dat
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
backup  3456 root    5r   REG  253,0  5242880 7890 large_file.dat

Now you know process 3456 has it open. Check if it’s safe to kill or wait for it to finish.

Scenario 2: Port conflict on application startup

$ ./myapp
Error: Address already in use (port 3000)

$ lsof -i :3000
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    4567 dev    10u  IPv4  45678      0t0  TCP *:3000 (LISTEN)

You find a stale node process. Kill it and restart your app.

Scenario 3: Disk space mysteriously consumed

Sometimes processes hold deleted files open, preventing space reclamation:

$ lsof | grep deleted
apache  1234 www  5w  REG  253,0  10737418240  12345 /var/log/apache/access.log (deleted)

That’s 10GB held by a deleted log file. Restart apache or truncate the file descriptor to reclaim space.

Scenario 4: Finding loaded libraries

$ lsof -p 1234 | grep '\.so'

This shows all shared libraries loaded by process 1234—useful for debugging library version issues.

Output Formatting and Scripting

For scripts, lsof’s default output is hard to parse. Use -F for field-separated output:

$ lsof -F pcn -i :80
p1234
cnginx
p1235
cnginx

Each field is prefixed: p for PID, c for command, n for name. This is parseable but ugly. For most scripts, combining with standard tools works better:

$ lsof -i :80 | awk 'NR>1 {print $2}' | sort -u
1234
1235

This extracts unique PIDs from column 2, skipping the header.

Get process names using a specific file:

$ lsof /var/log/app.log | awk 'NR>1 {print $1}' | sort -u

One-liner to free a port by killing its process:

$ lsof -ti :8080 | xargs kill -9

The -t gives just PIDs, xargs passes them to kill. The -i option ensures you only get network-related PIDs.

Check if any process has files open in a directory before unmounting:

$ if lsof +D /mnt/data > /dev/null 2>&1; then
    echo "Files still open, cannot unmount"
  else
    umount /mnt/data
  fi

Monitor network connections in real-time:

$ watch -n 1 'lsof -i -P -n | grep ESTABLISHED'

The -P prevents port name lookup, -n prevents hostname resolution—both speed up output significantly.

Conclusion

Master lsof and you’ll solve problems in seconds that would otherwise take hours of guessing. The key is understanding that in Linux, everything really is a file—and lsof gives you complete visibility into what’s open, who opened it, and how it’s being used. Keep it in your troubleshooting toolkit alongside netstat, ss, and ps. When mysterious errors strike, lsof usually has the answer.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.