Skip to content

Fix: ENOSPC: System limit for number of file watchers reached

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

Quick Answer

How to fix the ENOSPC file watchers error on Linux by increasing the inotify watch limit, configuring VS Code, optimizing watched files, and handling Docker/WSL edge cases.

The Error

You start a development server, run a build tool, or open a project in VS Code, and the process crashes with this error:

Error: ENOSPC: System limit for number of file watchers reached, watch '/home/user/project/src'

You may also see variations depending on the tool:

Error from chokidar (/home/user/project/src): Error: ENOSPC: System limit for number of file watchers reached, watch '/home/user/project/src'

In webpack or Create React App:

[webpack-dev-server] Error: ENOSPC: System limit for number of file watchers reached
    at FSWatcher._handle.onchange (node:internal/fs/watchers:207:21)

In VS Code, you might see a notification:

Visual Studio Code is unable to watch for file changes in this large workspace.
Please follow the instructions link to resolve this issue.

Or when running Jest:

ENOSPC: System limit for number of file watchers reached, watch '/home/user/project/node_modules'

This error means your operating system has hit the maximum number of files it can monitor for changes at the same time. Every tool that watches files for hot reloading, live compilation, or file syncing uses a kernel feature called inotify, and that feature has a hard cap.

Why This Happens

On Linux, the kernel uses inotify to notify applications when files change. Each file or directory being watched consumes one inotify watch. The system has a configurable limit on how many watches a single user can create, controlled by the kernel parameter fs.inotify.max_user_watches.

The default value on most Linux distributions is 8192 watches. That sounds like a lot, but modern JavaScript projects can easily exceed it. A single node_modules directory can contain tens of thousands of files and subdirectories. When you run a development server that watches your project for changes, it often recursively watches every file and directory under your project root — including node_modules.

Stack multiple projects, add VS Code (which also watches files), a running test runner, and maybe a Docker container with its own watchers, and you blow past 8192 in seconds.

The math is simple: if you have 30,000 files in node_modules, 2,000 files in your source tree, and VS Code is watching all of them, you need 32,000+ watches. The default limit of 8192 is not enough. This is similar in nature to running into permission issues on Linux where a system-level configuration blocks what should be a routine development task.

Fix 1: Increase the Watcher Limit Temporarily

The fastest fix is to increase the limit for the current session using sysctl. This change will be lost when you reboot.

sudo sysctl fs.inotify.max_user_watches=524288

This sets the limit to 524,288, which is more than enough for most development workflows. You can verify it took effect:

cat /proc/sys/fs/inotify/max_user_watches

You should see 524288. Now restart your dev server or VS Code, and the error should be gone.

If you also hit a limit on inotify instances (less common, but possible when running many tools simultaneously), increase that too:

sudo sysctl fs.inotify.max_user_instances=1024

Fix 2: Increase the Watcher Limit Permanently

The temporary fix resets on reboot. To make it permanent, write the setting to the sysctl configuration file.

echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf

Then apply the changes without rebooting:

sudo sysctl -p

On some distributions using systemd, the preferred location is a dedicated file under /etc/sysctl.d/:

echo "fs.inotify.max_user_watches=524288" | sudo tee /etc/sysctl.d/50-file-watchers.conf
sudo sysctl --system

Both approaches work. The /etc/sysctl.d/ method is cleaner because it keeps custom settings separate from the base sysctl.conf file.

Verify the change persists:

cat /proc/sys/fs/inotify/max_user_watches

After a reboot, check again to make sure the setting survived. If it didn’t, make sure no other configuration file is overriding your value. Files in /etc/sysctl.d/ are read in alphabetical order, and a later file can overwrite an earlier one.

Fix 3: Check Your Current Inotify Usage

Before blindly increasing the limit, it helps to understand what is consuming your watches. You can see the current total number of watches in use:

find /proc/*/fdinfo -type f 2>/dev/null | xargs grep -c inotify 2>/dev/null | grep -v ':0$'

To see which processes are using the most inotify watches:

for pid in $(find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null | cut -d/ -f3 | sort -u); do
  count=$(grep -c inotify /proc/$pid/fdinfo/* 2>/dev/null)
  cmd=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')
  echo "$count $pid $cmd"
done | sort -rn | head -20

This shows you the top 20 processes by inotify watch count. You might discover that a forgotten background process — a stale dev server, an old nodemon instance, or a file sync tool — is hoarding watches. Killing those processes frees up watches immediately without changing any system settings.

You can also check the current limit and how close you are to hitting it:

# Current limit
cat /proc/sys/fs/inotify/max_user_watches

# Current usage (approximate)
find /proc/*/fdinfo -type f -exec grep -l inotify {} \; 2>/dev/null | wc -l

Fix 4: Configure VS Code File Watching

VS Code watches your entire workspace for changes to provide features like auto-import, file explorer updates, and Git integration. In large projects, this alone can consume thousands of watches.

You can tell VS Code to exclude directories from watching by adding a files.watcherExclude setting in your settings.json:

{
  "files.watcherExclude": {
    "**/node_modules/**": true,
    "**/.git/objects/**": true,
    "**/.git/subtree-cache/**": true,
    "**/dist/**": true,
    "**/build/**": true,
    "**/coverage/**": true,
    "**/.next/**": true,
    "**/tmp/**": true
  }
}

VS Code already excludes node_modules and .git objects by default, but explicitly listing them ensures they are always excluded. Add any build output directories, cache directories, or other large directories you don’t need VS Code to watch.

You can also reduce the search scope by configuring files.exclude and search.exclude:

{
  "files.exclude": {
    "**/node_modules": true,
    "**/dist": true
  },
  "search.exclude": {
    "**/node_modules": true,
    "**/dist": true,
    "**/coverage": true,
    "**/package-lock.json": true
  }
}

These settings prevent VS Code from indexing or searching those directories, which reduces memory usage and watch consumption. If VS Code still struggles with your workspace, check that you do not have module resolution issues that cause the editor to scan unnecessary directories.

Why this matters: The default limit of 8,192 inotify watches was set when projects had hundreds of files, not tens of thousands. A single node_modules directory in a mid-sized React project can contain 30,000+ files, blowing past the limit before your dev server even finishes starting.

Fix 5: Ignore node_modules in Your File Watcher

Many build tools and dev servers watch node_modules by default, which is almost never what you want. You can configure your tools to ignore it.

webpack (via watchOptions):

// webpack.config.js
module.exports = {
  watchOptions: {
    ignored: /node_modules/,
  },
};

nodemon:

{
  "ignore": ["node_modules", "dist", "build", ".git"]
}

Save this as nodemon.json in your project root, or pass the flag directly:

nodemon --ignore node_modules/ --ignore dist/ server.js

Vite:

Vite already ignores node_modules by default, but if you have custom watch targets, make sure they are scoped tightly:

// vite.config.js
export default {
  server: {
    watch: {
      ignored: ['**/node_modules/**', '**/dist/**'],
    },
  },
};

chokidar (used by many tools internally):

const chokidar = require('chokidar');

const watcher = chokidar.watch('./src', {
  ignored: /(^|[\/\\])\../, // ignore dotfiles
  persistent: true,
  ignoreInitial: true,
});

By restricting the watcher to ./src instead of ., you avoid watching node_modules, dist, and other top-level directories entirely. If you run into issues where Node.js cannot resolve your modules after making these changes, see how to fix Cannot find module errors.

Fix 6: Fix for Docker and WSL Environments

Running a dev server inside Docker or Windows Subsystem for Linux (WSL) introduces unique file watching problems.

Docker with bind mounts:

When you mount a host directory into a Docker container with -v, inotify events from the host do not propagate into the container on some setups. The container’s file watcher never sees changes. Common workarounds:

  1. Use polling instead of inotify. Most tools support a polling fallback:
# webpack
CHOKIDAR_USEPOLLING=true npm start

# or in webpack config
module.exports = {
  watchOptions: {
    poll: 1000, // Check for changes every second
  },
};
# nodemon
nodemon --legacy-watch server.js
  1. Increase the inotify limit inside the container. Docker containers share the host kernel’s inotify limit by default, but you may need to set it explicitly:
# docker-compose.yml
services:
  app:
    build: .
    volumes:
      - .:/app
    # Required for inotify to work
    privileged: true

Or set the sysctl on the host, since containers inherit the host kernel’s limits.

  1. Use Docker volumes instead of bind mounts for node_modules:
services:
  app:
    volumes:
      - .:/app
      - /app/node_modules  # Anonymous volume — not watched from host

This prevents the host’s node_modules from being mounted into the container, which reduces the number of files the watcher needs to track. This is also a good practice for avoiding Docker permission errors caused by UID mismatches between host and container.

WSL 2:

WSL 2 runs a real Linux kernel, so inotify works natively — but only for files stored on the Linux filesystem. Files on the Windows filesystem (mounted under /mnt/c/) do not generate inotify events reliably.

If your project is at /mnt/c/Users/you/project, move it to the Linux filesystem:

# Move project to Linux filesystem
cp -r /mnt/c/Users/you/project ~/project
cd ~/project
npm install

Performance will improve dramatically. File watching will work without polling, and npm install will be significantly faster because the Linux ext4 filesystem handles many small files far better than the Windows NTFS filesystem accessed through the 9P protocol.

If you must keep the project on the Windows filesystem, enable polling:

CHOKIDAR_USEPOLLING=true npm start

Fix 7: Reduce the Number of Watched Files

Sometimes the best fix is to simply watch fewer files. If your project has grown large, audit what is actually being watched.

Clean up unused dependencies:

# Remove packages not listed in package.json
npm prune

# Find unused dependencies
npx depcheck

Fewer packages in node_modules means fewer files to watch. If your node_modules is bloated and you suspect broken installations, see how to fix Node.js module resolution errors for cleanup steps.

Use .gitignore-aware watching:

Many tools respect .gitignore by default. Make sure your .gitignore includes:

node_modules/
dist/
build/
coverage/
.next/
.cache/
*.log
tmp/

Split large monorepos:

If you have a monorepo with dozens of packages, consider configuring your tools to only watch the packages you are actively developing, not the entire tree:

# Instead of watching everything
npm run dev

# Watch only the specific package
npm run dev --workspace=packages/my-app

Delete stale projects:

If you have multiple old projects open in VS Code or running dev servers in the background, each one consumes watches. Close tabs, stop servers, and kill orphaned processes:

# Find and kill orphaned node processes
ps aux | grep node
kill <pid>

# Or kill all node processes (use with caution)
killall node

Fix 8: Use Alternative Watch Mechanisms

If increasing the inotify limit is not possible (restricted systems, shared hosting, CI environments), you can switch to alternative watch mechanisms.

Polling mode:

Most file watchers support a polling fallback that does not use inotify at all. It periodically stats files to detect changes. It is slower and uses more CPU, but it works regardless of the inotify limit.

# Environment variable used by chokidar (and tools that use it)
export CHOKIDAR_USEPOLLING=true
export CHOKIDAR_INTERVAL=3000  # Poll every 3 seconds to reduce CPU usage

For webpack:

module.exports = {
  watchOptions: {
    poll: 2000,
    aggregateTimeout: 500,
    ignored: /node_modules/,
  },
};

The aggregateTimeout setting delays the rebuild after the first change is detected, which batches rapid changes into a single rebuild.

Use watchman as an alternative backend:

Facebook’s Watchman is a more efficient file watching service that handles large projects better than raw inotify. Jest and Metro (React Native bundler) both support Watchman natively.

# Install watchman
sudo apt-get install watchman

# Or on Fedora/RHEL
sudo dnf install watchman

Once installed, Jest will automatically use Watchman instead of inotify-based watching. Watchman manages its own watch state efficiently and can handle millions of files without hitting system limits.

Disable watch mode entirely in CI:

In CI/CD pipelines, you do not need file watching at all. Run your builds and tests in single-run mode:

# Jest
jest --watchAll=false

# React Scripts
CI=true npm test

# webpack
webpack --mode production  # No watch

This avoids the ENOSPC error entirely in environments where you have no control over system settings. If your CI builds fail with module resolution errors alongside the watcher error, fix the module issue separately — it is unrelated.

How Other Platforms Handle File Watching

ENOSPC is a Linux-specific error because inotify is a Linux kernel feature. The “watch too many files” problem exists on every OS, but each one solves it with a different kernel API, and each one has its own failure mode. Understanding what your tool is doing under the hood makes it much easier to debug.

Linux — inotify:

A per-user, per-process limit (max_user_watches, max_user_instances). Watches are explicit and consume a small amount of kernel memory each. Recursive directory watching is not built-in — userland tools recursively add a watch per directory, which is what blows past the limit.

macOS — FSEvents:

A path-based, recursive-by-default API. You subscribe to changes under a directory, and the kernel delivers a coalesced stream of events. There is no per-watch limit because the API is fundamentally different — you do not register one watch per file. The trade-off is granularity: FSEvents delivers events with a coarser timestamp (the kernel can coalesce nearby changes), which is fine for editors but occasionally causes confusion in tools that expect a strict 1:1 mapping between writes and events.

Windows — ReadDirectoryChangesW:

A directory-handle-based API. You open a handle on a directory and the kernel notifies you when its contents change. Like FSEvents, it does not impose a “max watchers” limit but it has its own quirks: on network drives the API may silently drop events, and watching the root of a drive can flood your queue.

BSD / older macOS — kqueue:

Requires opening a file descriptor per watched file. The limit is the process file-descriptor limit (ulimit -n), which is usually lower than the inotify watch limit. This is why projects that need to watch large trees on BSD often hit “too many open files” instead of ENOSPC.

How JavaScript watchers abstract over all of this:

  • chokidar is the dominant abstraction in Node.js tooling (used by webpack, nodemon, Vite’s older modes, and many CLI tools). It picks the best native backend per platform and falls back to polling (usePolling: true) when the native API fails or is unavailable. The CHOKIDAR_USEPOLLING=true env var is the universal escape hatch.
  • watchman (Meta) runs as a long-lived daemon that maintains its own watch state and answers queries from clients. It is the backend Jest and Metro prefer because it survives the watcher process being killed and restarted, and it deduplicates watches across multiple clients on the same machine.
  • parcel-watcher is a native module written in C++ that uses inotify / FSEvents / ReadDirectoryChangesW directly. Parcel and many newer tools use it because it avoids the JS-level overhead of subscribing to thousands of inotify events.
  • Node.js built-in fs.watch has historically been unreliable across platforms — it inherits the platform’s quirks (event coalescing on macOS, missed events on network drives, no recursion on Linux). Node 20 added a recursive: true option that works on Linux too, but production tools still tend to wrap chokidar around it. If fs.watch itself is misbehaving, see Fix: Node.js fs.watch not working.

Practical implication:

If your build chain switches backends (e.g., Vite moving to parcel-watcher, Jest preferring watchman), the symptoms of “too many watchers” may change shape. The ENOSPC string is a hard signal that inotify is the backend in play — if you see it, you are on Linux (or a Linux container), and the fixes in this article apply directly.

Still Not Working?

The Limit Is Set but the Error Persists

If you’ve increased max_user_watches and confirmed the new value with cat /proc/sys/fs/inotify/max_user_watches, but still get the error, check max_user_instances:

cat /proc/sys/fs/inotify/max_user_instances

The default is often 128. Each inotify instance (not watch) corresponds to one inotify_init() call. A single Node.js process might create multiple instances. Increase it:

echo "fs.inotify.max_user_instances=1024" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

The Error Returns After Reboot

Your sysctl setting might be overridden. Check for conflicting files:

sudo sysctl --system 2>&1 | grep max_user_watches

This shows all files that set the value. The last one wins. If a system package drops a file in /etc/sysctl.d/ that sets a lower value with a higher alphabetical name (like 99-defaults.conf), it will override your setting. Rename your file to sort after it, or edit the conflicting file directly.

You Are Running Multiple Linux Users

The max_user_watches limit is per user. If you run your dev server as one user and VS Code as another (or root), each user has their own limit. Make sure the limit is high enough for the user that is actually hitting it.

systemd Services Are Consuming Watches

Some systemd services (like systemd-journald, snapd, or custom services) use inotify watches. If your system is watch-heavy even without development tools running, audit system-level consumption using the diagnostic script from Fix 3.

Network Filesystems

inotify does not work on NFS, CIFS/SMB, or other network filesystems. If your project is on a network share, file watching will silently fail or throw errors. Use polling mode, or move the project to a local filesystem. This same class of issue can cause unexpected behavior in other areas too, similar to how React hydration mismatches can silently cause problems that only surface later.

Containerized Development Environments (Dev Containers, Codespaces, Gitpod)

Remote development environments often have restrictive inotify limits that you cannot change. In these cases:

  1. Use polling mode as described in Fix 8.
  2. Scope your watchers tightly to only src/ or your source directories.
  3. Use the workspace’s built-in terminal settings to set CHOKIDAR_USEPOLLING=true in your shell profile.

Multiple Tools Share the Same Process Tree

If you launch your dev server, ESLint daemon, TypeScript watch mode, and tests from a single npm-run-all command, every child process inherits the same user’s inotify limit. The sum of their watches is what triggers ENOSPC, even when each tool alone would stay under the limit. Diagnose by counting watches per PID using the script in Fix 3 — you will often see two or three processes each holding 5,000+ watches. The fix is to consolidate or to launch the heavy watchers in separate terminals/users when iterating.

inotifywait Fails Even Though Your Build Tool Works

Some users hit ENOSPC when running inotifywait or entr from the command line, but their main dev server still seems fine. This usually means the dev server has already consumed the budget and inotifywait is the straw that breaks it. Free up watches before running ad-hoc CLI watchers:

# Kill stale watchers from previous sessions
pkill -f "node.*watch"
pkill -f "tsc.*--watch"

Then re-run inotifywait. If it still fails, raise the limit globally (Fix 2).

Snap-Packaged Editors and Sandboxed inotify

Editors installed via Snap (VS Code, IntelliJ, Sublime on some Linux distros) run inside a confinement that has its own inotify counters separate from the host’s. Even with max_user_watches=524288 set, the snap may still hit a lower internal cap. Switch to the .deb / official tarball install, or set --classic confinement where supported. On Ubuntu, the official VS Code .deb from Microsoft does not have this restriction.


Related: Fix: Cannot find module | Fix: bash permission denied | Fix: docker permission denied | Fix: Module not found: Can’t resolve | Fix: Too many re-renders

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles