Two sysctls, two limits, both per user:
| Setting | Limits | Default |
|---|---|---|
fs.inotify.max_user_instances | inotify instances one user may create | 128 |
fs.inotify.max_user_watches | watches one user may attach | 8,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
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
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
When a limit is hit the watcher cannot be installed and the tool degrades:
ENOSPCin 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
- exclude
node_modulesfrom watching on both sides — the bundler’swatchOptions.ignoredand 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.