Two sysctls, two limits, both per user:

SettingLimitsDefault
fs.inotify.max_user_instancesinotify instances one user may create128
fs.inotify.max_user_watcheswatches one user may attach8,192

An instance is one inotify_init() / inotify_init1() file descriptor — an fsnotify group, an event queue, the descriptor, and indexing structures for its watches. A watch is one attachment to an inode, bookkeeping linking watch, instance and inode.

Many tools opening listeners exhaust instances; few listeners over many files exhaust watches.

Ceilings, not reservations Link to this section

Raising either value costs nothing — upper bounds, not preallocated memory — but every instance and watch consumes kernel memory:

  • per instance, a modest fixed cost plus an event queue growing with pending events
  • per watch, real bookkeeping, roughly ~1 KB on modern 64-bit Linux (estimates from ~540 bytes on 32-bit upward)

At 524,288 watches the worst case is 500 MB of kernel memory — not reserved, but real if used. Watch count, not instance count, is usually the memory driver.

Why a monorepo gets there Link to this section

inotify reports created, edited, deleted and renamed files; every tool reacting to a source change uses it: the Next.js dev server, Turbopack or webpack watchers, Turborepo’s task runner, Vitest in watch mode, the Biome and TypeScript language servers, the editor, and pnpm’s workspace linking.

A pnpm + Turborepo monorepo with two Next.js apps, 23 shared packages and a 4.9 GB node_modules multiplies that: each app and package is a tree of directories, each tool opens its own instance, and pnpm’s symlinks reach the same real files through several paths. Raising max_user_instances from 128 to 512 addresses the per-tool listeners, not the file count, often fixing the symptom when max_user_watches is already generous.

What failure looks like Link to this section

When a limit is hit the watcher cannot be installed and the tool degrades:

  • ENOSPC in the output — the “too many open files” family, despite plenty of disk
  • hot reload stops firing, or fires for some packages and not others
  • the dev server falls back to polling — works, but costs CPU
  • language server features (completions, diagnostics) become slow or stop updating

Nothing crashes; the environment just becomes unreliable.

Reducing the demand Link to this section

  • exclude node_modules from watching on both sides — the bundler’s watchOptions.ignored and the editor’s file watcher
  • watch only the packages a task needs, not the whole workspace root
  • poll deliberately for trees the watcher cannot handle (network mounts, some VM shared folders), accepting the CPU cost
  • fewer listeners: one dev server instead of one per app when possible

Raising the limits is the cheap first move on a workstation. On a shared server or in containers the values are a resource question: the ceiling is per user, so a high value lets any process in that user consume it.