I still remember the first time a production deploy failed with a vague error: device busy. The release script was correct, permissions looked fine, and disk space was available. But one old worker process still had a file handle open inside the directory I wanted to rotate. The fix took seconds once I ran lsof and saw the exact PID holding the file. The hard part was knowing where to look.
That is why I treat lsof as one of the most useful Linux commands for debugging real systems. It does not just list files in the usual sense. It shows every open file-like object tied to processes: regular files, directories, sockets, pipes, devices, memory mappings, and network connections. In one view, you can answer: what is open, who opened it, and how it is being used.
If you work with APIs, containers, databases, CI runners, or local development stacks, this command saves time every week. I will show you how I read its output fast, which filters I use most, when to add sudo, how to inspect network ports, and how to avoid common mistakes. I will also share practical command patterns I use in 2026 when debugging on busy hosts.
Build the right mental model first
lsof means list open files, but Linux treats far more than documents as files. Think of the kernel as a giant switchboard and every process as a caller holding lines. Each line is a file descriptor (FD). A descriptor might point to:
- A regular file (
REG) - A directory (
DIR) - A terminal (
CHR) - A TCP or UDP socket (
IPv4,IPv6) - A pipe (
FIFO) - Memory-mapped content (
mem,mmap)
When you run lsof, you are asking the kernel: show me every open line and who owns it.
Basic syntax is simple:
lsof [options]
The raw command without filters can print a lot:
lsof
On a laptop, this might be hundreds or thousands of rows. On a server running many services, it can be huge. That is normal. Start broad, then filter.
I suggest you memorize this quick starter set:
What it does
—
-u Filter by user
-c Filter by command name prefix
-p Filter by PID
-i Show network files or sockets
-t Show only PIDs
+D Search open files under a directory
-nP Skip DNS and service name conversion
You can combine most of these. That is where lsof becomes very powerful.
Read default lsof output in 30 seconds
When you first see lsof output, it looks dense. I read columns in this order:
COMMAND(what process)PID(which instance)USER(who owns it)FD(how it is opened)TYPE(what kind of file object)NAME(path or endpoint)
Try this on your machine:
lsof | head -n 20
Important FD values you will see often:
cwd: current working directory of the processtxt: executable text file (binary)mem: memory-mapped file segmentmmap: mapped device or file area (platform-dependent display)0u,1u,2u: stdin, stdout, stderr
I tell juniors to think of FD like sockets on a power strip. Every device plugged in has an index and mode.
Important TYPE values:
REG: regular fileDIR: directoryCHR: character deviceIPv4orIPv6: network socket endpointsFIFO: named pipe
Example output fragment you might see:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 8821 api cwd DIR 1,4 4096 1234567 /srv/orders
python3 8821 api txt REG 1,4 1849200 7654321 /usr/bin/python3.12
python3 8821 api 7u IPv4 0,0 0 0t0 TCP 0.0.0.0:8080 (LISTEN)
python3 8821 api 11r REG 1,4 524288 1122334 /var/log/orders/current.log
From four rows, I can quickly infer:
- Process
python3PID8821runs from/srv/orders - It listens on TCP
8080 - It has the app log currently open
That is exactly the context I need during outages.
High-value filters I use every day
1) Show files opened by one user
When I suspect a service account is holding handles:
lsof -u deploy
This is useful during deploy scripts and release cleanup. If my script says it cannot remove a file, I check the account that owns long-running processes.
I can also exclude a user with caret syntax:
lsof -u ^root
That gives open files for everyone except root. I use this to reduce noise when root-owned daemons dominate output.
2) Filter by command name
To inspect processes by name prefix:
lsof -c nginx
lsof -c mysql
lsof -c node
-c matches command names by prefix. For many services that is enough. If I run multiple similar binaries, I move to PID filtering for exact results.
3) Filter by PID
When I need one specific process instance:
lsof -p 22451
This is my go-to after finding a suspect PID from ps, top, htop, or system logs.
I can pass multiple PIDs as a comma-separated list:
lsof -p 22451,22499,22514
4) PIDs-only mode for scripts
For shell automation, -t is gold because it outputs only process IDs:
lsof -t -i :3000
I can feed that directly into other commands:
kill -TERM $(lsof -t -i :3000)
If multiple PIDs are possible, I use xargs safely:
lsof -t -i :3000 | xargs -r kill -TERM
I prefer -TERM first and reserve -KILL for stubborn cases.
5) Find open files under a directory
The most common release issue: why can I not unmount, remove, or rename this directory?
sudo lsof +D /srv/releases/2026-02-06
+D walks that directory tree and shows processes with open handles there. This can be heavy on large trees, so I use it carefully.
A frequent typo: many people write -D for this use case. For recursive directory search, use +D.
6) Combine with grep for quick focus
lsof plus grep stays useful for fast terminal triage:
lsof -nP | grep ‘/var/log‘
lsof -nP -iTCP -sTCP:LISTEN | grep ‘:5432‘
I still use grep for simple narrowing even though structured tools exist.
Network troubleshooting with lsof (ports, listeners, clients)
lsof is excellent for port conflicts and socket inspection.
Who is listening on a port?
sudo lsof -nP -iTCP:8080 -sTCP:LISTEN
Flags explained:
-iTCP:8080: only TCP endpoint on port 8080-sTCP:LISTEN: listening sockets only-nP: do not resolve DNS names or service names
I nearly always include -nP in network checks. Without it, lookups add delay and output may show service names instead of raw ports.
Show all listening TCP sockets
sudo lsof -nP -iTCP -sTCP:LISTEN
I use this when auditing exposed services after deploy.
Show UDP activity
sudo lsof -nP -iUDP
This helps when debugging DNS forwarders, telemetry agents, game servers, or media components that rely on UDP.
Inspect one process network handles
lsof -nP -a -p 8821 -i
-a means logical AND, so this returns network files for PID 8821 only. I use this when one service has many file handles and I only care about sockets.
Quick command chain for port already in use
I use this sequence in production and local dev:
sudo lsof -nP -iTCP:3000 -sTCP:LISTEN
lsof -p
kill -TERM
sudo lsof -nP -iTCP:3000 -sTCP:LISTEN
If the last step still prints a listener, I check supervisor restarts such as systemd, PM2, Docker restart policy, or Kubernetes controller behavior.
Real-world incidents where lsof saves hours
Here are the situations where I reach for lsof first.
1) Deleted file still consuming disk
A classic Linux surprise: I remove a huge log file but disk usage does not drop. Usually, a process still has the file descriptor open.
Find deleted-but-open files:
sudo lsof -nP | grep ‘(deleted)‘
Typical output has a path ending in (deleted) with an active PID. I then decide whether to restart that process or rotate logs correctly.
Practical workflow:
sudo lsof -nP | grep ‘(deleted)‘
lsof -p
sudo systemctl restart orders-api.service
df -h
In my experience, this explains many sudden disk pressure incidents.
2) Cannot unmount device or directory busy
When umount says target is busy:
sudo lsof +D /mnt/backups
I usually find one of these:
- A shell with
cwdinside that path - A lingering backup process
- An editor or file watcher still attached
Then I close sessions or stop services in a controlled order.
3) NFS or remote mount delays
For network mounts that freeze tasks, lsof can reveal blocked file handles tied to specific PIDs. I pair it with ps -fp and service logs to decide whether to wait, stop, or fail over.
4) CI runner cleanup failures
CI jobs that build containers or temporary artifacts sometimes leave file handles open if a step crashes. I use:
lsof +D /tmp/runner-workspace
Then I terminate stragglers and rerun cleanup. This is especially common with parallel test workers.
5) Debugging app behavior with memory-mapped files
High-performance apps like databases, search engines, and model-serving runtimes often map files into memory. lsof rows with mem or mmap help me confirm what binary, library, or data segment is active. This matters when validating rollouts and version pinning.
Performance and safety tips on busy systems
lsof can be heavy if I run it without focus on large hosts. These habits keep it fast and safe.
Use -nP by default for speed and clarity
I treat this as baseline:
lsof -nP
Skipping DNS and service lookups usually turns noticeable pauses into fast responses. On busy systems, focused queries often complete in tens to hundreds of milliseconds, while unfiltered scans can take longer.
Filter early, not late
Good:
lsof -nP -p 8821
lsof -nP -iTCP:443 -sTCP:ESTABLISHED
Less good:
lsof | grep 8821
The first approach asks the kernel for less data up front.
Use sudo when permissions hide data
Without elevated rights, I may miss details from other users processes. If output seems incomplete, I rerun with sudo:
sudo lsof -nP -iTCP -sTCP:LISTEN
I only run elevated commands when needed, but for system-wide incident response it is often required.
Pipe to pager for large output
Avoid flooding terminal buffer:
lsof -nP | less
I also like sorted views for readability:
lsof -nP sort -k1,1 -k2,2n less
Be careful with recursive directory scans
+D can walk big trees and take time. I narrow the path first. If possible, I inspect likely subpaths instead of the entire mount.
Script defensively around empty output
When chaining with xargs, I use -r to avoid accidental runs with empty input:
lsof -t -iTCP:9090 -sTCP:LISTEN | xargs -r kill -TERM
That tiny -r avoids noisy failures.
Advanced filtering patterns I use in production
Once the basics are easy, these patterns make triage much faster.
Combine filters with -a for precise queries
By default, some selectors can behave like a broad match. I use -a to force logical AND behavior:
lsof -nP -a -u app -iTCP:8443 -sTCP:ESTABLISHED
This returns only established TCP 8443 connections owned by user app.
Exclude noisy states
If I only care about clients and not listeners:
lsof -nP -iTCP -a -sTCP:^LISTEN
The caret negates state selection. This is useful during connection leak investigations.
Scope by protocol family quickly
lsof -nP -i4
lsof -nP -i6
When dual-stack setups behave differently, this helps isolate IPv4 versus IPv6 issues.
Focus on one mount with +f --
For filesystem-level debugging, I sometimes combine file-system reporting and path targeting:
lsof +f -- /var
This helps when I suspect a specific mount or filesystem boundary issue.
Select by parent process workflow
lsof does not directly filter by PPID, so I pair it with ps:
ps -eo pid,ppid,cmd | grep ‘‘
lsof -p ,,
This catches workers spawned by a master process that still hold stale files.
Output formats for automation and repeatable runbooks
Humans read tables, but scripts need stable parsing. lsof supports machine-friendly output.
Use -t when only PID matters
lsof -t -iTCP:5432 -sTCP:LISTEN
This is ideal for stop, restart, and health scripts.
Use -F for field output
Field mode gives delimiter-friendly records:
lsof -F pcfn -p 8821
I use this for automation where parsing whitespace tables is fragile.
Common field letters I use:
p: PIDc: command namef: file descriptorn: name path or network endpoint
Snapshot before and after changes
A simple but powerful pattern:
lsof -nP -p 8821 > /tmp/lsof.before
deploy or restart
lsof -nP -p 8821 > /tmp/lsof.after
diff -u /tmp/lsof.before /tmp/lsof.after
This makes post-change validation objective instead of guesswork.
Edge cases and gotchas most guides skip
Kernel threads and pseudo-entries
Some entries may look odd or incomplete because they are kernel-managed or permission-limited views. I avoid over-interpreting single lines without process context.
Short-lived processes disappear quickly
By the time I run lsof, a process may already exit. For intermittent issues, I run repeated sampling:
while true; do lsof -nP -iTCP:8080 -sTCP:ESTABLISHED; sleep 1; done
Then I correlate timestamps with app logs.
Zombie processes are not the same as open handles
A zombie process is mostly an exit record waiting to be reaped. It typically does not keep normal file handles open in the way active processes do. If I see zombies, I investigate parent process behavior.
Containers and namespaces can mislead
Inside a container, lsof sees that namespace, not always the whole host picture. For host-wide truth, I run it on the host or use a privileged debug context.
SELinux and hardened policies
On hardened systems, visibility can be restricted even as root depending on policy and environment hardening. If output seems unexpectedly sparse, I verify policy context and auditing logs.
When I use lsof vs when I do not
lsof is incredible, but not always the first or only tool.
I use lsof when:
- I need process-to-file mapping fast
- A directory, mount, or file is busy
- A port conflict blocks startup
- Disk space is stuck after deleting logs
- I need exact PID ownership before killing anything
I do not rely on lsof alone when:
- I need deep packet-level network diagnosis
- I need historical behavior across hours
- I need kernel-level latency attribution
- I need distributed, multi-host timeline correlation
In those cases I pair it with ss, tracing tools, observability stacks, and service logs.
Alternative approaches and how they compare
Best for
Limitation
—
—
lsof Process-to-file and socket ownership
Heavy if run too broad
ss Fast socket state snapshots
Less file-centric context
fuser Find processes using file or mount
Less rich output than lsof
ps Process metadata
Does not show full open-file mapping
Deep runtime telemetry
Higher setup complexityMy rule: start with lsof for ownership truth, then branch to specialized tools.
2026 workflow: lsof with modern tooling and AI assistance
lsof remains relevant even with eBPF observability stacks, container dashboards, and AI copilots. I use it as a fast local truth source, then correlate with richer telemetry.
Traditional flow vs current flow
Traditional terminal-only flow
—
netstat then manual PID lookup
lsof -nP -iTCP: -sTCP:LISTEN then AI-assisted triage notes Guess logs and restart random services
lsof grep ‘(deleted)‘, identify exact PID, restart one unit
Trial-and-error stop sequence
lsof +D then ordered shutdown plan App logs only
lsof -p plus trace timeline from observability stack Small helper script I keep in my toolbox
This script prints listener details for a port and suggests a graceful stop command.
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo ‘Usage: ./port-owner.sh ‘
exit 1
fi
port="$1"
echo "Checking TCP listeners on port ${port}..."
if ! sudo lsof -nP -iTCP:"${port}" -sTCP:LISTEN; then
echo "No TCP listener found on port ${port}."
exit 0
fi
pids="$(sudo lsof -t -iTCP:"${port}" -sTCP:LISTEN | sort -u)"
if [[ -z "${pids}" ]]; then
echo ‘No PID found.‘
exit 0
fi
echo "Listener PID(s): ${pids}"
echo ‘Run this to stop gracefully:‘
echo "kill -TERM ${pids}"
Run it like this:
chmod +x ./port-owner.sh
./port-owner.sh 8080
I often pair outputs like this with incident copilots that summarize changes between runs, but I still trust lsof output as the final source.
Container and namespace note
On containerized hosts, lsof inside a container only shows that namespace context. If I need host-level truth, I run on the host or in a privileged debug pod with proper permissions.
Common mistakes I see (and how I avoid them)
Mistake 1: forgetting -nP
Symptom: command feels slow and output has unexpected names.
Fix: add -nP for most diagnostics.
Mistake 2: using command name filter as exact match
-c is prefix matching. -c app may match multiple binaries.
Fix: switch to -p when exact process scope matters.
Mistake 3: assuming no output means no issue
Without sudo, I might not see other users handles.
Fix: rerun with elevated rights where policy allows.
Mistake 4: killing processes before checking supervisors
I kill a PID and it immediately returns.
Fix: inspect service manager first with systemctl status, container policies, or orchestrator controllers.
Mistake 5: scanning huge directory trees casually with +D
Symptom: command takes long and adds pressure on busy disks.
Fix: narrow to subdirectories or isolate candidate PIDs first and inspect with -p.
Mistake 6: relying on one command only
lsof is excellent, but I pair it with:
ss -ltnpfor quick socket snapshotsps -fpfor command contextsystemctl statusfor service state- Logs and traces for timeline context
This combination gives faster root-cause confirmation.
A practical command cookbook you can copy today
I keep this short list near my runbooks.
# All open files (large output)
lsof -nP
Files opened by user deploy
lsof -nP -u deploy
Everyone except root
lsof -nP -u ^root
All files opened by processes named nginx
lsof -nP -c nginx
Open files for exact PID
lsof -nP -p 24871
Processes listening on TCP 80
sudo lsof -nP -iTCP:80 -sTCP:LISTEN
Any process using port 5432 (TCP or UDP)
sudo lsof -nP -i :5432
Show only established TCP connections
lsof -nP -iTCP -sTCP:ESTABLISHED
Show UDP handles only
lsof -nP -iUDP
Directory busy investigation
sudo lsof +D /opt/myapp/releases
Deleted files still held open
sudo lsof -nP | grep ‘(deleted)‘
PIDs only for automation
lsof -t -iTCP:3000 -sTCP:LISTEN
Graceful kill whatever listens on 3000
lsof -t -iTCP:3000 -sTCP:LISTEN | xargs -r kill -TERM
Combine PID and network filters (AND)
lsof -nP -a -p 24871 -i
IPv6-only sockets
lsof -nP -i6
Machine-friendly fields
lsof -F pcfn -p 24871
Production checklist I follow during incidents
When I am tired at 2 AM, I do not trust memory. I trust checklists.
- Confirm symptom clearly (port conflict, disk not freeing, mount busy, cleanup failure).
- Run minimal targeted
lsofquery with-nP. - Re-run with
sudoif visibility seems incomplete. - Capture PID, command, and file or socket name.
- Confirm process context with
psand service manager. - Apply least-destructive action first (
TERM, graceful restart, stop one unit). - Re-run the same
lsofquery to verify resolution. - Write a short note in the incident log with command and outcome.
This loop reduces bad guesses and accidental downtime.
Security and compliance considerations
lsof output can expose sensitive paths, user names, and network endpoints. In regulated environments, I treat outputs as operational data.
- I avoid sharing raw
lsofdumps in public channels. - I sanitize hostnames, user names, and internal IPs before ticket sharing.
- I store incident artifacts in controlled systems with retention policies.
- I run least privilege by default and escalate only for targeted checks.
This keeps diagnostics useful without leaking sensitive internals.
Final thoughts
lsof is one of those tools that gets more valuable as systems grow more complex. It gives direct, kernel-backed visibility into one of the hardest debugging questions in Linux: who is holding what right now.
If you only remember a few patterns, make them these: -nP, -p, -i, +D, and -t. Those five options cover most real incidents I see in production.
The biggest mindset shift is simple: stop guessing and ask the system directly. lsof is often the fastest path from vague symptom to precise action.



