A legitimate file descriptor peak rises with active connections or open work and falls after that work ends. A descriptor leak keeps resources open after the application no longer needs them, so the count develops a rising baseline that eventually reaches the process, service, container, or system limit.
The distinction matters because both conditions can produce the same final error. Raising the descriptor limit may be correct capacity planning for a busy reverse proxy, but it only delays failure when sockets, files, pipes, or watchers are never released.
What Pattern Defines a Legitimate Descriptor Peak?
A normal peak follows workload concurrency. legitimate peaks track active workload, then drops as requests finish, sockets close, workers exit, and temporary files are released.
The baseline before and after the event remains similar. A backup window, media stream burst, or many concurrent web clients may produce a high count without indicating broken resource handling.
The peak should also correlate with completed work. If twice as many clients create roughly twice as many active sockets and the count returns afterward, the system is showing finite capacity demand rather than persistent loss.
What Pattern Reveals a Descriptor Leak?
A leak changes the baseline instead of only the maximum. leaks keep descriptors open after work ends, so every request cycle, reconnect, reload, or failed operation leaves some resources behind.
The count may grow slowly enough to hide during short tests. A service can look healthy for hours or days until the remaining descriptor headroom becomes too small for the next connection or file open.
Restarting the process resets the count because the kernel closes its descriptors, but that recovery does not prove the underlying problem is fixed. The same slope returns after the service begins handling work again.
Why Do Sockets, Files, and Watchers Produce Different Curves?
Linux uses descriptors for several I/O resource types, and different resource types create different growth patterns. Each type therefore needs a different workload explanation.
Client sockets should follow concurrent sessions. Log or media files should follow active handles. Pipes may follow child processes, while watcher-related descriptors can remain stable even though the number of watched paths grows through a separate kernel limit.
Classifying descriptors by target is more useful than reading one total. Hundreds of expected sockets during a traffic burst differ from steadily growing deleted log files or repeated connections to one unavailable dependency.
Why Does Raising the Limit Delay a Leak Instead of Fixing It?
The `Too many open files` error occurs only when growth reaches a ceiling. higher limits only postpone leak exhaustion.
A higher limit stretches the time between restart and failure. That can make the service appear repaired during a short observation window while allowing the leak to consume more kernel memory and more network or storage state.
Capacity changes should therefore follow evidence that descriptors are released normally. Otherwise the new ceiling is a larger failure envelope rather than a stability improvement.
Which Measurements Separate Capacity From Lifecycle Failure?
The total count is only the first signal. descriptor age and type reveal the root cause. Track count, target type, open duration, creation rate, close rate, traffic, and completed requests on the same timeline.
For a peak, descriptor count should move with concurrency and eventually return. For a leak, open duration and baseline rise while the amount of active useful work does not increase proportionally.
Compare multiple cycles instead of one snapshot. A single high count cannot show whether the process is near the top of a normal wave or partway through a persistent upward trend.
When Is a Higher Descriptor Limit Actually Justified?
connection reuse lowers legitimate descriptor demand. Before increasing limits, remove avoidable connection churn, bound pools, and confirm that resources close when work completes.
A larger limit is justified when tested legitimate concurrency approaches the current effective service limit, descriptor counts return to baseline, and memory, socket buffers, backend pools, and recovery behavior can support the higher demand.
Set alerts below the hard failure point and preserve administrative headroom. The goal is not to make the limit unreachable; it is to keep normal peaks inside a measured operating range while detecting abnormal growth early.
| Observed Pattern | Likely Meaning | Next Check |
|---|---|---|
| Count rises and falls with traffic | Legitimate concurrency peak | Capacity-test the service limit |
| Baseline rises after every cycle | Descriptor leak | Classify unclosed resources by type and age |
| Restart resets count, then slope returns | Lifecycle defect remains | Trace open and close paths |
| Shell limit differs from service failure point | systemd or container limit mismatch | Inspect the running process limits |
FAQ
Can a file descriptor leak occur with low CPU usage?
Yes. A process can retain sockets or files while waiting and consume almost no CPU until a new allocation fails.
Does TIME_WAIT prove a descriptor leak?
No. TIME_WAIT is TCP kernel state after a socket closes. A descriptor leak means the application still holds an open descriptor.
Why does restarting the service appear to fix it?
Process exit closes its descriptors and restores headroom. If the application lifecycle remains broken, the count begins growing again.
Should alerts use a fixed descriptor count?
Use both percentage of the effective limit and growth behavior. A stable high count may be normal, while a lower but steadily rising count may be dangerous.
Final Takeaway
Legitimate descriptor peaks follow active work and return to a stable baseline. Leaks retain resources after work ends, creating a rising floor that eventually crosses a finite limit. Diagnose the curve, resource type, and descriptor age before raising limits, because extra headroom supports real capacity only when the lifecycle is already correct.
Tech & AI HUB
More to Read

Runtime State vs Persistent State in Home Assistant: What Must Survive Restart?
Home Assistant does not persist every live value; config, registries, selected restored states, history, and deployment data play different restart roles.

How Does Home Assistant Authenticate Local and Remote Sessions?
Local and remote Home Assistant sessions use the same server-side identity model; remote access changes the route and TLS boundary, not the core token...

Why Can Home Assistant History Queries Slow as Recorder Data Grows?
Recorder growth can raise History query cost when the requested range touches more rows, cache misses increase, or storage and index work become slower.

