Node.js Express app showing unbounded memory growth traced to accumulating event listeners and timers
Environment
Node.js 18 application running Express behind PM2 on Linux, single-process mode, serving REST APIs; observed memory growth over days under typical traffic causing occasional OOM kills from system OOM killer; third-party libraries include an MQTT client and a database driver.
Symptoms
Resident set size (RSS) and heapUsed grow steadily over time; response latency increases when memory is high; processes crash without uncaughtException logs when OOM occurs; heap snapshots show many instances of closures and EventEmitter listeners tied to routes and long-lived connections.
Observations
Heap snapshots taken at 6-hour intervals show retained objects increasing in the same code paths; process._getActiveHandles reveals many timers created from setInterval that are not cleared; running --trace-warnings shows 'MaxListenersExceededWarning' for an EventEmitter in a third-party module that is created per request; removing a specific middleware reduces growth rate but does not fully stop it.
Already attempted
Upgraded Node and dependencies to latest minor versions, added global logging for new connections and disconnections, instrumented GC with --trace-gc, took multiple heap snapshots and compared retained object paths using Chrome DevTools, added process.env.NODE_OPTIONS='--max-old-space-size' as temporary mitigation and configured PM2 to restart on memory threshold.
Question
What targeted diagnostic steps will reliably identify the code paths retaining listeners and timers, and what reversible code changes can I deploy to stop the leak without removing entire middleware features?
Verified community troubleshooting
No answers yet..
Incident analysis
Most likely causes
Summary
Suggested Fixes
Continue troubleshooting
Related troubles
Matched from the title, description and tags