Error: EMFILE: too many open files, open '/app/uploads/0412.jpg'

Node.js · updated 2026-11-03

Thrown by fs and net calls when the process already holds as many file descriptors as the operating system allows (ulimit -n, often 256 on macOS and 1024 on Linux). Sockets and file watchers count too, not just files.

Error: EMFILE: too many open files, open '/app/uploads/0412.jpg'
    at async open (node:internal/fs/promises:639:25)
    at async Object.readFile (node:internal/fs/promises:1242:14)
    at async Promise.all (index 3871)
    at async processUploads (file:///app/src/jobs/thumbnails.js:22:3) {
  errno: -24,
  code: 'EMFILE',
  syscall: 'open',
  path: '/app/uploads/0412.jpg'
}

Quick fix

Look for code that opens many files or connections at once, usually Promise.all over a big list. Process them in batches or with a concurrency limit, make sure every stream and handle is closed, and only then raise the limit (ulimit -n or LimitNOFILE) if the workload truly needs more.

  1. Check the current limit with ulimit -n, and count open descriptors with lsof -p <pid> | wc -l.
  2. Replace Promise.all over thousands of items with batches or a concurrency limit (p-limit, or a simple queue).
  3. Close what you open: use fs.promises with await, call stream.destroy() on errors, and close database and HTTP clients you create per request.
  4. In development, a file watcher on a big folder (node_modules) can hit the limit; exclude it or use polling.
  5. Raise the limit for the service once the leak is ruled out: LimitNOFILE in systemd, --ulimit in Docker.
typescript
import pLimit from 'p-limit';
import { readFile } from 'node:fs/promises';

const limit = pLimit(50); // at most 50 files open at once

// Before: await Promise.all(paths.map((p) => readFile(p)));
const buffers = await Promise.all(paths.map((p) => limit(() => readFile(p))));

Why it happens

Unbounded parallel work

Promise.all over thousands of files, images or URLs opens all of them at once and runs past the limit partway through the list.

Leaked handles

Streams that error without being destroyed, files opened with fs.open and never closed, or HTTP agents and database clients created per request keep descriptors open until the process runs out.

File watchers in development

Watchers on large trees (node_modules, build output) use one descriptor per watched path on some systems and hit low macOS limits quickly.

A low default limit

macOS defaults to 256 per shell and many Linux services to 1024, which a busy server with many sockets can legitimately exceed.

Other fixes

  • graceful-fs queues open calls when it sees EMFILE instead of failing; useful for tools, not a fix for leaks.
  • Reuse one HTTP agent with keepAlive instead of creating clients per request.

Same error, other wording

  • Error: EMFILE: too many open files, watch '/app/node_modules'
  • Error: connect EMFILE 10.0.0.5:5432 - Local (undefined:undefined)
  • Error: ENFILE: file table overflow

Let your coding agent catch the next one

Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:

> Find the EMFILE errors in production, show me the code paths that were opening files or sockets when they hit the limit, and add a concurrency limit or close the leaked handles before raising any limits.

Plans from $29/moSee what your agent readsSee the setupCancel any time from the billing portal

Related errors

References: Node.js errors: common system errors (EMFILE), systemd docs: LimitNOFILE