lsof answers the questions every Linux administrator asks daily: which process holds this file open, what is listening on this port, who is connected to my server right now, and what file descriptors are leaking? It is one of those tools where a single command often replaces a half-hour of investigation. This guide covers the lsof invocations that solve real problems and the security-audit patterns that catch malicious processes hiding in plain sight.
The mental model
To Linux, almost everything is a file: regular files, directories, sockets, pipes, devices, anonymous inodes. lsof walks /proc/*/fd across every process and reports a unified view. With root permission you see everything; as a regular user you see only your own processes.
sudo lsof | wc -l # total open FDs system-wide (can be huge)
sudo lsof -nP | head # -n: skip DNS, -P: skip port-name lookup (much faster)
What is listening on a port
sudo lsof -nP -iTCP -sTCP:LISTEN
sudo lsof -nP -i:443 # specific port
sudo lsof -nP -iTCP:80,443,8080 # multiple ports
sudo lsof -nP -iUDP # UDP listeners
sudo ss -ntlpu # modern alternative
ss is faster on busy systems and ships with iproute2; lsof is more flexible because it cross-references files and sockets in one tool.
Who has connected to my server
sudo lsof -nP -iTCP -sTCP:ESTABLISHED
sudo lsof -nP -i@1.2.3.4 # connections to/from a specific IP
sudo lsof -nP -i:443 -sTCP:ESTABLISHED # who is on port 443 right now
Each row shows the local process, peer address, and TCP state โ useful during incident response to identify what is connected when a service is misbehaving.
Files held by a process
sudo lsof -p 1234 # all FDs of PID 1234
sudo lsof -p $(pgrep -f myapp)
sudo lsof -p 1234 -a -d^cwd,^txt,^mem # exclude noise (cwd, text, mmap)
sudo lsof +D /var/log # everything open under a directory
+D is invaluable when a filesystem cannot be unmounted: umount: target is busy, then sudo lsof +D /mnt/data reveals exactly which process holds files on it.
Deleted-but-still-open files
Disk full but du shows little usage? Almost certainly a process holding an unlinked file:
sudo lsof | grep '(deleted)' | head
sudo lsof -nP +L1 # FDs whose link count is <= 1 (usually 0)
sudo find /proc/*/fd -lname '*(deleted)' 2>/dev/null
The space is reclaimed only when the process closes the FD or restarts. Common culprits: log files unlinked while syslog or the application still writes to them; shared-memory segments after process crash.
Per-user view
sudo lsof -u www-data # everything held by user
sudo lsof -u ^root # exclude root
sudo lsof -u www-data -i # only network FDs for that user
Counting FDs per process (leak detection)
for p in $(pgrep -f myapp); do
fds=$(ls /proc/$p/fd 2>/dev/null | wc -l)
echo "$p $fds"
done | sort -k2 -n | tail
ls -1 /proc/$(pgrep -f myapp | head -1)/fd | wc -l
Sample over time: a process whose FD count climbs steadily without bound is leaking sockets, files, or pipes. Identify what type with the next command.
Categorising open FDs
sudo lsof -p $(pgrep -f myapp | head -1) | awk '{print $5}' | sort | uniq -c
# Output column 5 is FD type: REG, DIR, IPv4, sock, FIFO, CHR, BLK, unix, ...
A leak of IPv4 means sockets not closed (typical for a misbehaving HTTP client). A leak of REG means files. A leak of FIFO often means an unread pipe between subprocesses.
Security audit: hidden processes and odd listeners
# 1. Processes listening on unexpected high ports
sudo lsof -nP -iTCP -sTCP:LISTEN | awk 'NR > 1 && $9 ~ /:[0-9]{4,5}$/'
# 2. Processes connected to non-standard outbound ports
sudo lsof -nP -iTCP -sTCP:ESTABLISHED | awk '{print $9}' | grep -oE ':[0-9]+$' | sort | uniq -c | sort -rn
# 3. Files open in /tmp or /var/tmp (common attacker staging)
sudo lsof | awk '$NF ~ /^\/tmp\// || $NF ~ /^\/var\/tmp\//' | head
# 4. Binaries running from world-writable directories
sudo lsof | awk '$4=="txt" && ($NF ~ /^\/tmp/ || $NF ~ /^\/dev\/shm/)'
Each category surfaces a different post-exploitation pattern. Run during incident response or weekly as a baseline.
Watching changes in real time
sudo lsof -r 5 -nP -iTCP -sTCP:LISTEN # repeat every 5 sec
watch -n 2 'sudo lsof -nP -iTCP:443 -sTCP:ESTABLISHED | wc -l'
The audit script
#!/bin/bash
echo "== Listening sockets =="
sudo lsof -nP -iTCP -sTCP:LISTEN | tail -n +2 | awk '{printf "%-20s %-30s %s\n",$1,$9,$2}'
echo
echo "== Outbound connections =="
sudo lsof -nP -iTCP -sTCP:ESTABLISHED | tail -n +2 | awk '{print $1,$9}' | sort -u | head
echo
echo "== Deleted files held open =="
sudo lsof | grep '(deleted)' | awk '{printf "%-15s pid=%-6s size=%-10s %s\n",$1,$2,$7,$9}' | head
echo
echo "== Top FD consumers =="
for p in $(ps -eo pid --no-headers); do
c=$(ls /proc/$p/fd 2>/dev/null | wc -l)
[ "$c" -gt 100 ] && echo "$c $(ps -p $p -o comm=) ($p)"
done | sort -rn | head -10
Common pitfalls
- Running lsof without
-n -Pon a busy server; DNS and service-name lookups can take minutes. - Trusting
lsof -iafter an attacker has loaded a kernel rootkit โ usess -tnpas a cross-check. - Forgetting that
+Dwalks the directory; on huge trees this is slow. Prefer+d(one-level) when possible. - Counting FDs with
lsof | wc -lwhen you really want process-level granularity.
lsof is the swiss-army knife of "what is happening on this host right now." Burn the listed and established invocations into muscle memory; combine with ss for network-only views; run the audit script weekly. Most "mystery process" tickets resolve in under a minute with one of these commands.