Node.js EMFILE: too many open files
Fix the Node.js EMFILE error when the process exhausts file descriptors through unclosed streams, watchers, or connection pools.
Symptoms
- Error: EMFILE: too many open files, open '/path'
- fs.watch or chokidar stops emitting after a while
- Server cannot accept new sockets under sustained load
Possible causes
- File streams opened in a loop are never closed
- Watchers or sockets accumulate because listeners are not destroyed
- A connection pool size exceeds the OS file-descriptor limit
- Recursive directory reads open thousands of files concurrently
Diagnosis steps
Count descriptors used by the process
ls /proc/<pid>/fd | wc -lInterpretation
A number close to ulimit indicates descriptor exhaustion; growth over time points to a leak.
Check the per-process limit
ulimit -nInterpretation
The limit may be a low default (1024). Raising it buys time but does not fix an unclosed handle.
Inspect open handles via inspector or lsof
lsof -p <pid> | wc -lInterpretation
Repeated paths show the leak source so you can close streams at the right place.
Root cause
Every open file, socket, and watcher consumes one file descriptor. Node does not close handles for you, so code that opens resources in loops or on every request gradually exhausts the OS limit, after which any open call fails with EMFILE.
Fix
Close every stream in a finally block, destroy watchers and sockets when done, and bound concurrency when reading many files. If a higher limit is genuinely needed, raise ulimit -n and the systemd LimitNOFILE setting, and cap pool sizes accordingly.
Verify the fix
Watch the descriptor count with ls /proc/<pid>/fd | wc -l while exercising the load path; the count should stay flat instead of climbing, and EMFILE should not recur.
Symptoms
Operations begin failing with EMFILE: too many open files after the process has run for a while, or immediately if thousands of resources are opened concurrently. Restarting the process clears it temporarily.
Diagnosis
Count the descriptors in use and compare with the limit:
ls /proc/$(pgrep -f 'node server.js')/fd | wc -l
ulimit -n
Then use lsof -p <pid> to see which paths dominate. A recurring path reveals the leak.
Fix
Close resources deterministically and cap concurrency:
const handle = await fs.promises.open(p, 'r');
try {
// ...read...
} finally {
await handle.close();
}
For bulk reads, batch with p-limit instead of launching everything at once, and destroy watchers on shutdown.
Verification
Re-apply load and re-check the descriptor count. It should plateau rather than grow, and no new EMFILE errors should appear.