lsof Command in Linux: Practical Guide with Real Examples

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:

Option

What it does

Typical use —

-u

Filter by user

Find files opened by one account -c

Filter by command name prefix

Check one service family -p

Filter by PID

Inspect a specific process -i

Show network files or sockets

Port and connection debugging -t

Show only PIDs

Script-friendly output +D

Search open files under a directory

Why is this path busy -nP

Skip DNS and service name conversion

Faster and clearer output

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 process
  • txt: executable text file (binary)
  • mem: memory-mapped file segment
  • mmap: 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 file
  • DIR: directory
  • CHR: character device
  • IPv4 or IPv6: network socket endpoints
  • FIFO: 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 python3 PID 8821 runs 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 cwd inside 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: PID
  • c: command name
  • f: file descriptor
  • n: 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

Tool

Best for

Strength

Limitation

lsof

Process-to-file and socket ownership

Direct and practical mapping

Heavy if run too broad

ss

Fast socket state snapshots

Very fast and concise

Less file-centric context

fuser

Find processes using file or mount

Quick for one target

Less rich output than lsof

ps

Process metadata

Great command context

Does not show full open-file mapping

eBPF tools

Deep runtime telemetry

Powerful low-level visibility

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

Task

Traditional terminal-only flow

2026 practical flow —

— Port conflict

netstat then manual PID lookup

lsof -nP -iTCP: -sTCP:LISTEN then AI-assisted triage notes Disk not freeing

Guess logs and restart random services

lsof

grep ‘(deleted)‘, identify exact PID, restart one unit

Busy directory

Trial-and-error stop sequence

lsof +D then ordered shutdown plan App file behavior

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 -ltnp for quick socket snapshots
  • ps -fp for command context
  • systemctl status for 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 lsof query with -nP.
  • Re-run with sudo if visibility seems incomplete.
  • Capture PID, command, and file or socket name.
  • Confirm process context with ps and service manager.
  • Apply least-destructive action first (TERM, graceful restart, stop one unit).
  • Re-run the same lsof query 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 lsof dumps 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.

Scroll to Top