Quick Diagnosis
1. Monitor – Log process.memoryUsage() (RSS/heap) for 24 h.
2. Profile – Run node --inspect-brk with Chrome DevTools or clinic.js.
3. Check Config – Verify N8N_EXECUTIONS_PROCESS, N8N_MAX_EXECUTION_TIMEOUT, and Docker --memory limits.
4. Apply Fixes – Reduce V8 heap, switch to queue execution, add periodic GC.
5. Validate – Memory should stay within 10 % drift over 48 h; otherwise treat it as a leak and file an issue.
Who this is for: Operators of self‑hosted n8n (Docker, Kubernetes, or bare‑metal) who see daily RSS growth and need a systematic way to decide whether the cause is a leak or a normal load pattern.
We cover this in detail in the n8n Performance Degradation & Stability Issues Guide.
| Symptom | Immediate Check | Likely Category |
|---|---|---|
| RSS climbs > 50 % daily, then crashes | docker stats <container> or pm2 list → Memory column |
Potential leak |
| Spikes only while a specific workflow runs | Enable workflow‑level logs (N8N_LOG_LEVEL=debug) and isolate the workflow |
Design‑induced load |
| Memory plateaus after restart, then rises again | docker restart n8n and watch for repeat pattern |
Leak |
| Steady under low traffic, jumps under high concurrency | Simulate load with k6 or artillery |
Design/scale issue |
Quick actionable solution (run once)
Capture a baseline memory snapshot:
docker exec -it n8n node -e "console.log(process.memoryUsage())"
Restart with tighter limits:
docker run -d \ -e NODE_OPTIONS="--max-old-space-size=256 --expose-gc" \ -e N8N_EXECUTIONS_PROCESS=queue \ --memory=512m \ --restart=unless-stopped \ n8nio/n8n
If memory stabilises, the problem was over‑allocation; otherwise move to profiling (section 2).
1. How n8n Manages Memory Internally?
If you encounter any n8n workflows slow after weeks in production root cause analysis resolve them before continuing with the setup.
n8n runs on Node.js v18 LTS. Its memory usage falls into three buckets:
| Bucket | What it stores | Typical size |
|---|---|---|
| Heap | JS objects, workflow definitions, execution data | 50‑200 MB |
| RSS | Native C++ addons, V8 engine, OS buffers | 100‑300 MB |
| External | DB connections, file handles, Docker overlay | 20‑80 MB |
Execution models and their memory profile
| Mode | Description | Memory impact |
|---|---|---|
| main (default) | All steps run in the same process | Large heap for long‑running workflows |
| queue | Steps delegated to BullMQ workers (child processes) | Lower per‑process heap, higher overall RSS |
| process | Each execution spawns a fresh Node.js process | Near‑zero retention, best for leak‑prone workloads |
EEFA note: For > 10 concurrent executions, queue is the production‑grade choice; process eliminates most leak vectors at the cost of extra CPU.
2. Identify a Real Memory Leak
2.1 Continuous monitoring (24‑48 h)
Add a lightweight logger to your Docker Compose file:
services:
n8n:
image: n8nio/n8n
environment:
- NODE_OPTIONS=--max-old-space-size=256
command: >
sh -c "while true; do
node -e \"console.log(JSON.stringify(process.memoryUsage()))\"
sleep 300
done & exec n8n start"
Collect the JSON logs, plot **RSS** vs. **heapUsed**, and look for a linear upward trend that does not reset after a graceful restart.
2.2 Heap snapshot with clinic
npm i -g clinic # install once clinic doctor -- node ./packages/cli/src/cli.js start
Run typical traffic for 30 min, then open clinic.html. Focus on:
- Detached objects (rare in n8n)
- Large arrays such as
executionDatathat remain after a workflow finishes - Unclosed DB connections (
MongoClient,PostgresPool)
EEFA warning: Run clinic only in a staging environment; the profiler adds ~15 % CPU overhead. If you encounter any why n8n execution time increases over time resolve them before continuing with the setup.
2.3 Automated leak‑detection script (cron)
#!/usr/bin/env bash
THRESHOLD=0.10
BASE=$(docker exec n8n node -e "console.log(process.memoryUsage().heapUsed)")
sleep 21600 # 6 h
CURRENT=$(docker exec n8n node -e "console.log(process.memoryUsage().heapUsed)")
DIFF=$(node -e "console.log((${CURRENT}-${BASE})/ ${BASE})")
if (( $(echo "$DIFF > $THRESHOLD" | bc -l) )); then
docker restart n8n
fi
Schedule it with crontab -e (e.g., 0 */6 * * * /path/to/script.sh).
3. Design Patterns That Inflate Memory
| Pattern | Why it inflates memory | Mitigation |
|---|---|---|
Large JSON payloads kept in executionData |
Full payload is retained for downstream nodes | Enable “Clear execution data after each node” or trim payload with a Set node |
Recursive sub‑workflows (Execute Workflow node) |
Each sub‑workflow creates a new execution context that lives until the parent finishes | Limit depth via N8N_MAX_SUB_WORKFLOW_DEPTH and run sub‑workflows in a separate process |
| Infinite loops in Function nodes | JavaScript loops that never break keep objects alive | Add explicit break/return and enforce N8N_MAX_EXECUTION_TIMEOUT |
Heavy file buffers (Read Binary File) |
Files are fully loaded into memory before being passed downstream | Use the streaming variant or off‑load to external storage (S3, MinIO) |
Unbounded concurrency (N8N_MAX_EXECUTIONS default 100) |
Simultaneous executions multiply heap usage | Reduce to a realistic value (e.g., 20) and rely on a queue worker pool |
EEFA tip: For SaaS‑style deployments, set N8N_EXECUTIONS_PROCESS=process to isolate each user’s workflow in its own process.
4. Configuration Tweaks to Contain Memory
| Env variable | Recommended value | Effect |
|---|---|---|
| NODE_OPTIONS | –max-old-space-size=256 –expose-gc | Caps V8 heap, enables manual GC |
| N8N_EXECUTIONS_PROCESS | queue (or process for high‑risk) | Moves heavy steps to workers |
| N8N_MAX_EXECUTION_TIMEOUT | 300000 ms (5 min) | Forces GC after timeout |
| N8N_WORKER_TIMEOUT | 60000 ms (1 min) | Auto‑kill idle workers |
| N8N_MAX_WORKERS | 4 (adjust to CPU cores) | Limits concurrent workers |
| N8N_LOG_LEVEL | error (prod) | Reduces log‑buffer pressure |
| Docker –memory | 512m‑1g (depends on load) | Enforces cgroup limit, triggers OOM if exceeded |
Docker Compose example (split for readability)
services:
n8n:
image: n8nio/n8n
environment:
- NODE_OPTIONS=--max-old-space-size=256 --expose-gc
environment:
- N8N_EXECUTIONS_PROCESS=queue
- N8N_MAX_EXECUTION_TIMEOUT=300000
- N8N_MAX_WORKERS=4
deploy:
resources:
limits:
memory: 1g
restart: unless-stopped
EEFA warning: Setting --max-old-space-size too low can cause “JavaScript heap out of memory” crashes. Validate with a staging load before production rollout. If you encounter any n8n slows down even with low cpu usage resolve them before continuing with the setup.
5. Automated Cleanup & Garbage Collection
5.1 Manual GC trigger (Node ≥ 14)
docker exec -it n8n node -e "
if (global.gc) {
global.gc();
console.log('GC run');
} else {
console.log('Run with --expose-gc');
}"
Schedule the command (e.g., every 4 h) via a cron job inside a side‑car container.
5.2 Periodic worker restart (queue mode)
# Gracefully kill all BullMQ workers; they will respawn automatically docker exec n8n pkill -f "worker.js"
5.3 PM2 auto‑restart on memory threshold
pm2 start n8n --name n8n \ --max-memory-restart 300M \ --node-args="--max-old-space-size=256"
PM2 restarts the process once RSS exceeds 300 MB, preventing OOM cascades.
6. When to Scale Instead of Fix
| Situation | Recommended action |
|---|---|
| Memory stabilises after config changes (≤ 10 % drift) | Keep settings, continue monitoring |
Heap repeatedly hits --max-old-space-size ceiling |
Increase heap **and** move to process mode |
| > 30 concurrent executions cause RSS > 800 MB | Deploy a horizontal n8n cluster behind a load balancer; use Redis for BullMQ |
| Persistent leak after profiling | Open a GitHub issue with heap snapshots; consider contributing a fix |
Scaling checklist
- Deploy Redis (
REDIS_HOST) for BullMQ persistence. - Enable
N8N_CLUSTER_MODE=trueand setN8N_CLUSTER_INSTANCE_ID. - Set Kubernetes
replicas: 3(or more) with a shared DB. - Configure health checks (
/healthz) to auto‑evict unhealthy pods.
Conclusion
If memory stabilises after applying tighter heap limits, queue‑based execution, and periodic garbage collection, you are dealing with a design‑related consumption pattern that can be mitigated with configuration and workflow‑design changes. Persistent linear growth despite those measures indicates a real memory leak that requires heap‑snapshot analysis and, ultimately, code fixes upstream. Start with the quick‑diagnosis steps, move to systematic monitoring and profiling, and only then decide whether to adjust configuration, refactor workflows, or scale the deployment. This disciplined approach keeps n8n reliable in production while avoiding unnecessary over‑provisioning.



