Article View

Scroll down to read the full article.

The Silent Killer: Node.js fs.watch and NFS Event Black Holes on EC2

calendar_month August 17, 2026 |
Quick Summary: Fix Node.js fs.watch silently dropping events on NFS mounts in EC2. Troubleshoot inotify limits, kernel issues, and Node.js polling fallbacks for ...

The Silent Killer: Node.js fs.watch and NFS Event Black Holes on EC2

Alright, listen up. You've been there. Your Node.js application is supposed to react to file changes – config updates, content deploys, whatever. You're using fs.watch, because that's what Node.js gives you. Everything's fine in dev. But in production, specifically on an EC2 instance with an NFS mount for your shared data? Things just… stop. Silently. No errors. No logs. The changes happen, but your app is blissfully ignorant. You spend days, maybe weeks, tearing your hair out. Welcome to the club. This isn't a fluke; it's a specific, insidious problem that bites far too many teams.

A complex
Visual representation

I’ve seen this exact scenario play out on critical systems, leading to stale caches, incorrect deployments, and even data inconsistencies. It's infuriating because it's a silent failure. Your metrics look fine, your app is running, but it's operating on outdated information. It's a fundamental break in expected behavior. Let’s get to the bottom of this.

The Problem: Missing File System Events on NFS

Your Node.js app, relying on fs.watch, fails to reliably detect file system changes on directories mounted via Network File System (NFS), especially when deployed on AWS EC2 instances running specific Linux kernels and older Node.js versions. The events just… disappear. No exceptions, no warnings, no crash. Just a critical part of your application logic simply not firing.

Affected Environments

This isn't universal, which is why it's such a pain to debug. It’s a specific confluence of factors. Here's where we've seen it hit hardest:

Operating System Kernel Version Node.js Version Range Filesystem Type
Amazon Linux 2 4.14.x LTS (4.14.173-137.229.amzn2.x86_64, etc.) 10.x, 12.x, early 14.x NFSv4
Ubuntu 18.04 LTS (Bionic) 4.15.x 10.x, 12.x NFSv4
CentOS 7 3.10.x 8.x, 10.x NFSv4

The Root Cause

Here’s the deal: fs.watch on Linux fundamentally relies on inotify. NFS, by design, doesn’t inherently propagate inotify events from the server to the client. Modern NFS configurations (especially NFSv4.1 with 'pNFS') *can* improve this, but often don't provide real-time, event-driven updates in the same way a local filesystem does. Instead, the NFS client often relies on caching and polling the server for changes.

The problem is compounded by two factors:

  1. inotify Limit Exhaustion: Node.js's fs.watch, especially when used with recursive: true on a large directory tree (common in content repos or shared configuration mounts), creates an inotify watch for every single subdirectory. Even if the default inotify limits (/proc/sys/fs/inotify/max_user_watches and max_queued_events) seem sufficient, a busy application or multiple applications on the same host can quickly exhaust these, leading to silent event drops (ENOSPC errors at the kernel level that Node.js often doesn't bubble up effectively).
  2. Faulty Polling Fallback: Node.js and its underlying libuv library *should* gracefully fall back to a polling mechanism (similar to fs.watchFile) when inotify fails or is unavailable on network file systems. However, on the specific kernel/Node.js combinations listed, we've observed that this fallback is either buggy, inefficient, or simply doesn't activate reliably when inotify hits its limits or gets inconsistent events from NFS. It gets into a weird limbo state where it thinks it's watching, but nothing happens. This behavior can be particularly elusive, making debugging excruciating. Some of the newer JavaScript runtimes like Bun might handle this better, but if you're stuck on Node, you need this fix.

The core issue is that the OS's contract for event-driven file changes is broken by NFS and then Node.js fails to reliably adapt.

The Fix: Kernel Tuning & Explicit Node.js Polling

You need to tackle this from two angles: ensure your kernel isn't dropping events, and then explicitly tell Node.js to use a more robust (albeit less performant) polling mechanism for network file systems.

Step 1: Increase inotify Limits (Client Side)

First, always check and increase your inotify limits on the EC2 client instance. Even if you think it's not the primary issue, insufficient limits will certainly exacerbate it. We typically aim for a substantial increase for any application that does recursive watching.

Check current limits:


cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_queued_events
  

Set higher limits (make this persistent across reboots):


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

This increases the maximum number of watches a user can create and the queue size for events. This alone might fix simpler cases, but for the truly stubborn ones on NFS, we need more.

Step 2: Force Node.js Polling on NFS

This is the critical, often-missed part. Node.js (via libuv) has an internal environment variable, UV_FS_METHOD, that can influence its file system watching behavior. Forcing it to poll on NFS mounts bypasses the flaky inotify interaction and ensures changes are detected, albeit with a slight delay determined by the polling interval.

This might introduce a slight increase in latency for change detection compared to instantaneous inotify events, but reliability often trumps sub-millisecond detection in these scenarios. Unless you're engineering something for absolute latency dominance, this trade-off is usually acceptable.

A microscopic view of a data stream
Visual representation

Here’s the complete command or configuration you need to apply to your Node.js application process. This should be set as an environment variable before your Node.js process starts.


# For specific Node.js applications or environments:
UV_FS_METHOD=poll node your-app.js

# For all Node.js applications started by a systemd unit, add to .service file:
# Environment="UV_FS_METHOD=poll"

# Or within a Dockerfile/docker-compose.yml:
# ENV UV_FS_METHOD=poll
  

What this does is instruct libuv to use a polling mechanism for file system events rather than relying solely on the native OS notification system (like inotify). This essentially tells Node.js, “Don’t trust the OS with network file systems; check manually.”

Verification

After implementing these changes:

  • Restart your Node.js application: The environment variable needs to be picked up.
  • Monitor logs: Check for any new inotify-related errors or warnings that might now appear.
  • Test thoroughly: Create, modify, and delete files in your NFS-mounted directory. Ensure your application reacts as expected. Observe the delays, if any, introduced by the polling.

Conclusion

This issue is a classic example of an obscure interaction between kernel versions, file system protocols, and application runtime behaviors. The fix isn't glamorous, but it's effective. Don't let silent failures cripple your production applications. Understand the underlying mechanisms, tune your environment, and give your applications the tools they need to function reliably, even in the messy reality of distributed systems. Your sanity, and your on-call team's sleep, will thank you.

Discussion

Comments

Read Next