A Linux process uses file descriptors for more than ordinary files. Network sockets, pipes, event descriptors, terminals, and many other kernel objects appear through the same integer-based interface.
When a service slowly accumulates descriptors, it may eventually fail with Too many open files, stop accepting connections, or behave unpredictably under load. Linux procfs provides enough information to investigate many descriptor leaks without installing extra tools.
Start by counting descriptors
For a known process ID:
ls /proc/$PID/fd | wc -lA single count is not enough to prove a leak. Observe it across time and workload. A server may legitimately open more descriptors as connection volume rises and then release them later.
A suspicious pattern is monotonic growth after traffic returns to normal.
Inspect what the descriptors point to
The /proc/$PID/fd directory contains symbolic links:
ls -l /proc/$PID/fdTypical targets include:
/var/log/example.log
socket:[348201]
pipe:[348712]
anon_inode:[eventpoll]
/tmp/work.bin (deleted)These target types narrow the search considerably. Hundreds of growing socket entries suggest a connection lifecycle problem. Repeated deleted files suggest temporary files are being unlinked but remain open.
Permissions apply: inspecting another user’s process may require elevated privileges, and hardened systems can restrict procfs visibility.
Understand deleted-but-open files
On Linux, removing a pathname does not free a file’s storage while a process still has it open.
A descriptor can therefore appear as:
/var/log/example.log (deleted)This commonly happens during log rotation or temporary-file handling. The path is gone from the directory, but disk space remains allocated until the final descriptor closes.
If disk usage stays high after files were deleted, inspect process descriptors for (deleted) targets before assuming filesystem accounting is wrong.
Compare descriptor counts with process limits
The process limit is visible through:
cat /proc/$PID/limitsLook for Max open files. A process with 2,000 open descriptors may be healthy if its workload and limit support that. The same count can be dangerous if the soft limit is 2,048.
Raising the limit can postpone failure, but it does not fix a leak. Limits should reflect expected capacity after lifecycle bugs are addressed.
Group descriptors by type
A rough shell inspection can show which target class dominates:
for fd in /proc/$PID/fd/*; do
readlink "$fd"
doneReview the output for repeated paths, many sockets, repeated pipe identifiers, or deleted files.
Be careful when automating this analysis: descriptors can close between directory listing and readlink, so transient No such file errors are normal for active processes.
Inspect descriptor metadata
Each descriptor also has an entry under /proc/$PID/fdinfo:
cat /proc/$PID/fdinfo/7Depending on the descriptor type, this can include file position, flags, mount information, and descriptor-specific details.
This is especially useful when several descriptors point to the same path and you need to determine whether they represent independent opens or a duplicated descriptor state.
Connect observations to application lifecycle
The operating system can show what is open, but the fix usually lives in application code.
Common causes include:
- response bodies that are not closed;
- files opened on error paths without deferred cleanup;
- sockets retained after timeouts;
- subprocess pipes not collected;
- watchers created repeatedly and never closed;
- temporary files unlinked but still referenced;
- retry loops that acquire a resource before releasing the previous one.
Map the growing descriptor type back to the code path that creates it.
Reproduce with a bounded workload
A useful diagnostic experiment is:
- record the descriptor count;
- run a fixed number of representative operations;
- wait for expected asynchronous cleanup;
- record the count again;
- repeat the same workload.
If each cycle leaves a stable number of additional descriptors, the relationship is easier to investigate than an uncontrolled production graph.
Also watch concurrency. A temporary increase during active requests is expected; the important question is whether descriptors return toward a baseline.
Avoid relying on descriptor numbers
File descriptor integers are reused. Descriptor 42 at one moment may refer to a socket and later to a file.
For time-series debugging, compare counts, target classes, and application operations rather than assuming one numeric descriptor has persistent identity.
Common pitfalls
Treating every high count as a leak
Connection-heavy services can legitimately hold many descriptors. Look for unexplained retention and growth.
Increasing ulimit first
A higher limit can turn a quick failure into slower resource exhaustion. Diagnose ownership and cleanup before treating capacity as the problem.
Forgetting deleted files consume space
Unlinked files remain allocated while open. Restarting the process releases them, but the durable fix is correct close and rotation behavior.
Expecting procfs to identify the source line
procfs shows kernel state. Pair it with application metrics, structured logging, profiling, or tracing to find the code that created the resource.
Make descriptor ownership explicit
Resource leaks become less common when code makes ownership obvious: the function that opens a resource should either close it or clearly transfer responsibility to another owner.
Linux gives every process a visible descriptor table through procfs. By watching how that table changes under controlled workloads, you can turn a vague Too many open files incident into a specific class of resource that the application failed to release.