Independent comparison · no paid rankings
Home / Blog / Node.js memory leak: watch the process before the forced restart
Technical

Node.js memory leak: watch the process before the forced restart

Restarting Node every night hides the leak — until the interval is not enough and PM2 loops on OOM kill.

4 min read Updated Jul 19, 2026

Node API stable at 512 MB RAM. One month later: PM2 restarts every four hours. Added max_memory_restart=800M — restart loop forty per minute at noon peak. Post-mortem: global Map cache keyed by userId without eviction, shipped with "temporary sessions" feature — slow leak, invisible in ten-minute test.

Node does not aggressively return memory to the OS — heapUsed can mislead after garbage collection. A leak is trend growth under representative load, not a post-deploy spike. Nightly restart hides the problem until the interval is no longer enough.

Monitor before restart

Measure what matters:

MetricSignal
process.memoryUsage().heapUsedTrend after garbage collection
RSSOOM killer watches RSS
Event loop lagLeak + load = event loop latency
GC pause frequencyRises if heap grows

Common tools:

  • Prometheus with node_exporter and app /metrics
  • PM2 pm2 monit, --max-memory-restart with alert if restart count rises
  • APM (Datadog, etc.) to correlate heap and slow requests

Practical rule: scheduled restart masks — alert RSS > baseline × 1.5 for one hour before adding a nightly cron.

Restart without a graph = paying for ambulance without diagnosis.

Heap diagnosis

Procedure in staging, then production off-peak:

  1. Reproduce long load (k6 for at least two hours).
  2. Capture heap snapshot at start and end — --inspect or --heapsnapshot-near-heap-limit (Node 18+).
  3. Compare in Chrome DevTools (Comparison) — objects with rising count.
  4. Walk retainers (Closure, Array, Buffer).

Common causes on web APIs:

  • setInterval never cleared with clearInterval
  • Duplicate socket.on without removeListener
  • Global { [reqId]: largeObject } never deleted
  • Logging library keeping context references

Hosting and memory limits

In container or Kubernetes, memory limit kills Node via SIGKILL without graceful shutdown. Size limit above max heap plus margin for native code — or fix the leak.

On VPS, swap masks then kills performance — alert on RSS, not CPU only. Single Node process: leak affects whole service. PM2 cluster mode does not fix leak — it multiplies it across workers.

Compare VPS offers suited to Node via the directory and compare tool. For deployment, cross-read PM2 in production.

PM2: restart vs fix

max_memory_restart: OK temporarily with priority investigation ticket.

Nightly cron restart: tolerable if no known leak (OS memory return quirk) — not a substitute for fix.

Pair max_restarts and exp_backoff_restart_delay to avoid restart loops cutting WebSockets at peak.

The peak: automatic restart is compounding debt

Here is what PM2 hides when everything looks stable.

Team culture: heap snapshot before adding max_memory_restart — or at least a dated root-cause ticket.

Decide and move forward without blind spots

Before adding a restart cron:

  1. Seven-day RSS graph under normal traffic — identify trend, not isolated spikes.
  2. Alert on PM2 restart count — not only HTTP uptime.
  3. Long load test in staging — at least two hours to reveal slow leak.
  4. Fix retainers identified in snapshot — Map without TTL, listeners, timers.
  5. Remove cron restart if memory curve stabilises after fix.

See PM2 deployment for supervision best practices, and the directory to pick a VPS with enough RAM for off-peak heap snapshot.

Frequently asked questions

How to detect Node.js memory leak?

Watch RSS or heapUsed over hours or days under stable traffic. If the curve rises without dropping after garbage collection, likely leak. Spike then drop is normal; rising plateau is not.

Is PM2 max_memory_restart enough?

Palliative — recycles before OOM but loses state, hides root cause, may loop. Pair with alert and heap investigation before considering the problem solved.

heapdump in production?

Possible off-peak with --heapsnapshot-near-heap-limit (Node 18+) or manually triggered heapdump module. Analyse in Chrome DevTools — avoid at peak without measuring impact.

Classic leaks on Node web APIs?

Closures keeping request references, event listeners not removed, Map cache without TTL, aggregated buffers, forgotten timers — each leaves RSS slowly climbing.


Watching the memory curve costs less than PM2 looping restarts the day the leak outruns cron.

Compare European hosts

Filter by compliance, location and use case — then open the sheets to verify the real scope.

Browse the directory
Blog

Related reading

All articles →