n8n crashes with "JavaScript heap out of memory": causes and fixes
Published 4 August 2026 · 8 min read
A workflow that used to run fine stops dead, the n8n interface becomes unreachable, and the logs show FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. Or worse: nothing at all — the container restarts silently, the execution stays frozen in "running" forever, and only a docker inspect reveals OOMKilled: true. In both cases the diagnosis is the same: the execution carried more data than the available memory could hold.
This guide explains why n8n is particularly exposed to this problem, how to identify the node and the data volume at fault, and walks through the five fixes in order of effectiveness — starting with the ones that treat the cause, not just the symptom.
Recognizing the symptom
The error shows up in several forms, depending on whether Node.js or the Linux kernel gives out first:
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
1: 0xb85bc0 node::Abort() [node]
2: 0xa94834 node::FatalError(char const*, char const*) [node]
...
That's the "clean" case: Node.js hits its own heap ceiling and says so before stopping. The other case is sneakier:
docker ps -a
# STATUS: Exited (137) 2 minutes ago
docker inspect --format '{{.State.OOMKilled}}' n8n
# true
Exit code 137 (128 + signal 9) means the kernel killed the process: the container exceeded its memory limit and the OOM killer took it down without warning — no message in the n8n logs, just a restart if you have restart: always. The third face is the most misleading: an execution stuck in "running" in the interface. The process died mid-processing, n8n never got to record the end of the execution, and the status will never be updated.
In every case, the trigger is the same: too much data in a single execution.
Why n8n runs out of memory
You need to understand a core property of n8n: all the data of an execution lives in memory, from one end of the workflow to the other. Each node receives the previous node's items, produces its own, and the whole set — including the intermediate results of nodes already executed — stays in the heap until the execution ends, notably to feed execution saving and step-by-step display.
The scenarios that blow up this model are always the same:
- Thousands of items at once: a
SELECTwithout aLIMIT, an API called without pagination, a full CRM export. 50,000 items of a few KB each, multiplied by the number of nodes traversed, and the heap overflows. - Binary data in memory: by default, files (PDFs, images, Excel) travel as base64 inside the execution data — an encoding that inflates their size by about a third. Ten 50 MB files are enough to bring the instance to its knees.
- Accumulating loops: a Loop Over Items loop keeps the results of all previous iterations in memory, since they belong to the same execution. The loop frees nothing.
- Large files to parse: an 80 MB Excel file becomes, once parsed into JSON, several hundred MB of JavaScript objects.
- Giant API responses: an endpoint returning 30 MB of JSON, immediately duplicated by every transformation node.
There's nothing n8n-specific about this mechanism. An empirical study by Lijie Xu, Wensheng Dou and colleagues, presented at IEEE ISSRE 2015 ("A Characteristic Study on Out-of-Memory Errors in Distributed Data-Parallel Applications" — see on Google Scholar), analyzed hundreds of real out-of-memory errors in data-processing applications: the dominant cause is not a lack of RAM on the machine, but processing that materializes too much data in memory at once — large intermediate results, data accumulated as the job progresses. That is word for word what happens in an n8n workflow loading 50,000 items: adding RAM pushes the wall back, reducing what gets materialized makes it disappear. Hence the order of the fixes that follow.
Fix 1 — reduce the data upstream
The most effective fix is also the least spectacular: let less data into the execution.
- Paginate at the source:
LIMIT/OFFSETin SQL, an API'spage/per_pageparameters, the HTTP Request node's pagination option. Process 500 items per execution rather than 50,000 in one go. - Filter server-side: a
WHEREclause, anupdated_sinceparameter — every item that never enters n8n is an item that weighs nothing. - Project: only fetch the fields you need. A
SELECT id, email, statusweighs ten times less than aSELECT *dragging free-text columns along. On the API side, look for afieldsparameter; on the n8n side, a Set node in "Keep Only Set" mode right after the source sheds the ballast before it travels through the whole workflow.
These reflexes are covered in detail in our guide on optimizing n8n workflow performance: in most cases, they alone make the error disappear.
Fix 2 — get files out of the heap with filesystem mode
If your workflow handles files, a single environment variable changes everything:
services:
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
volumes:
- n8n_data:/home/node/.n8n
In filesystem mode, binary data is written to disk and only a reference travels inside the execution data — instead of the full content encoded as base64 in the heap. The persistent volume is essential, since that's where the files land. For everything related to sizing, cleaning up those files and the limits of memory mode, see our guide on handling large files in n8n.
Fix 3 — split into batches and sub-workflows
The key point, often misunderstood: a Loop Over Items loop does not free memory between iterations, because everything belongs to the same execution. To genuinely release the pressure, delegate each batch to a sub-workflow via the Execute Sub-workflow node: the child execution processes its batch, finishes, and its memory is freed — the parent workflow only keeps the small result returned.
The typical pattern: a parent workflow that paginates (fix 1) or splits into batches of 200 with Loop Over Items, and calls a sub-workflow containing all the heavy logic (enrichment, parsing, writes) for each batch. Remember to limit what the child returns to the parent — a simple counter or status, not the processed items. The full setup (data passing, execution mode, error handling) is covered in our n8n sub-workflows guide.
Fix 4 — raise the Node heap… consistently with the container
Once the data is reduced, if your legitimate load still exceeds Node.js's default ceiling (on the order of 2 GB depending on the version and detected memory), raise it explicitly:
services:
n8n:
image: docker.n8n.io/n8nio/n8n
environment:
- NODE_OPTIONS=--max-old-space-size=4096
mem_limit: 5g
--max-old-space-size=4096 allows roughly 4 GB of V8 heap. The classic trap: setting a 4 GB heap inside a container capped at 2 GB. Node believes it has 4 GB, doesn't trigger its garbage collector aggressively, exceeds the real 2 GB… and the kernel kills the container — back to exit code 137, with no error message, right when you thought you had "increased the memory". The rule: the heap at roughly 75-80% of the container limit, the rest covering buffers, native code and Node's off-heap memory. And keep the lesson from the study cited above in mind: if the workflow materializes ten times too much data, 4 GB will overflow just like 2 GB — this fix complements the first three, it doesn't replace them.
Fix 5 — prune executions and switch to queue mode
Two hygiene measures round out the picture for busy instances. First, automatic pruning of past executions, which lightens the database and the data n8n handles:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 # in hours: 7 days
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none # don't store details of successful runs
How this pruning works in full (and the SQLite case, which needs one extra step) is detailed in our guide on cleaning up n8n executions.
Second, if your instance takes on heavy or concurrent loads, queue mode spreads executions across dedicated workers (EXECUTIONS_MODE=queue, Redis, one or more worker containers): a greedy workflow can kill one worker without taking down the interface or the webhooks, and you size memory per worker. That's the subject of our queue mode with Redis guide.
Diagnosing: which node, what payload size?
Before fixing anything, locate the culprit:
- Spot the last executed node: open the execution frozen in "running" — the last node marked as finished points to the next one as the likely suspect.
- Measure the payload: run the workflow on a small sample (add a
LIMIT 10), open the suspect node's output and extrapolate — 10 items for 40 KB, so 50,000 items for ~200 MB, before multiplication by the downstream nodes. - Watch consumption live:
docker stats n8nduring the execution shows the memory climb and the margin left before the limit. - Confirm the OOM kill:
docker inspect --format '{{.State.OOMKilled}}' n8nafter an unexplained restart settles the question between an application crash and a container execution.
Environment variables recap
| Variable | Purpose |
|---|---|
NODE_OPTIONS=--max-old-space-size=4096 |
V8 heap ceiling in MB (keep it below the container limit) |
N8N_DEFAULT_BINARY_DATA_MODE=filesystem |
Stores files on disk instead of the heap |
EXECUTIONS_DATA_PRUNE=true |
Enables automatic execution pruning |
EXECUTIONS_DATA_MAX_AGE=168 |
Maximum age of kept executions, in hours |
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none |
Doesn't store details of successful executions |
EXECUTIONS_MODE=queue |
Queue mode: executions offloaded to workers |
On the docker-compose side, add mem_limit (or deploy.resources.limits.memory under Swarm/Kubernetes) consistent with the heap.
In summary
"JavaScript heap out of memory" and OOMKilled are two faces of the same problem: an execution materializing more data than the memory can hold. Treat the cause before the symptom: paginate, filter and project upstream; get files out of the heap with N8N_DEFAULT_BINARY_DATA_MODE=filesystem; split large volumes into batches handed to sub-workflows, whose memory is freed with each child execution. Only then raise the heap with NODE_OPTIONS=--max-old-space-size, always below the container's memory limit — a Node ceiling higher than mem_limit guarantees a silent OOM kill. And for busy production instances, execution pruning and queue mode with workers turn a single point of failure into an architecture that takes the load.
FAQ
Frequently asked questions
What does the "JavaScript heap out of memory" error mean in n8n?
Node.js, the engine that runs n8n, hit its heap ceiling: the current execution carried more data than the allocated memory could hold. It's almost never an n8n bug but a workflow materializing too many items at once — a huge export, base64 binaries, an accumulating loop. First reduce the data per execution (pagination, filters, batches in sub-workflows), switch binary data to filesystem mode, and only then raise the ceiling with NODE_OPTIONS=--max-old-space-size.
Why does my n8n container get OOMKilled (exit code 137) with no error message?
Exit code 137 means the Linux kernel killed the container for exceeding its memory limit — before Node.js even reached its own heap ceiling, which is why there's no FATAL ERROR in the logs. Check with docker inspect --format '{{.State.OOMKilled}}' n8n. The fix: align the container limit (mem_limit) with the Node heap, keeping --max-old-space-size around 75-80% of the container limit.
What does NODE_OPTIONS=--max-old-space-size do in n8n?
This option sets the V8 heap ceiling of Node.js in megabytes: NODE_OPTIONS=--max-old-space-size=4096 allows roughly 4 GB. It must stay below the container's memory limit, otherwise the kernel will kill the process (OOMKilled) before Node can manage its memory properly. And it doesn't fix the cause: if the workflow loads ten times too much data, it will eventually saturate 4 GB just as it saturated 2 GB.
How can I process thousands of items in n8n without running out of memory?
Paginate at the source and only fetch the fields you need, then split the processing: Loop Over Items to form batches, and a sub-workflow (Execute Sub-workflow) that handles each batch — the child execution frees its memory when it finishes, which a plain loop inside the same workflow never does. Switch files to N8N_DEFAULT_BINARY_DATA_MODE=filesystem, and for recurring heavy loads, adopt queue mode with dedicated workers.
Bundle FlowKit Complet
€269