Why Heavy Code Nodes Spike n8n CPU and Memory Usage

Heavy Code node workflows push n8n's memory use up because n8n holds the output of every node in RAM for the entire execution, and your Code node adds its own copy of that data on top of what's already there. A workflow pulling 20,000 rows into a Code node isn't holding those rows once, it's holding them at every step that touched them, each row wrapped in n8n's item structure with json, binary and pairedItem keys. CPU matters less than most people expect: a single execution runs on one thread, so a slow Code node blocks the whole Node process instead of spreading across cores.

In practice, n8n resource usage with heavy Code node workflows lands almost entirely on RAM, and the fix is nearly always to move less data through fewer steps, then size the server for the peak rather than the average. What follows is what drives that usage, how to measure yours, and the settings that actually change the numbers.

n8n keeps every node's output in memory, which is why Code nodes get blamed for a problem they didn't cause

n8n passes items from node to node, and it retains all of that output for the length of the run. It has to. Expressions like $('HTTP Request').all() can reach back to any earlier node, the execution log needs the data to show you what happened, and error handling needs it to tell you which item failed.

So a ten step workflow that touches the same 20,000 records can be sitting on several copies of them at once. Memory doesn't come back the moment a node finishes either. It comes back when the execution ends and V8's garbage collector gets around to reclaiming it.

That's the whole shape of the problem. Your Code node's own footprint is tiny. The data volume flowing through the execution is what fills the heap.

Node.js caps how much memory a single process can use through its heap limit, which V8 sets from available system memory unless you override it with --max-old-space-size. Cross that ceiling and the process dies with FATAL ERROR: Reached heap limit Allocation failed, JavaScript heap out of memory. The Node.js project documents this flag alongside the rest of its runtime options, and n8n's own guidance points at the same setting for memory errors.

n8n's item format costs more than the raw JSON suggests

Each item is an object: { json: { ...your data }, binary: { ... }, pairedItem: ... }. The pairedItem tracking exists so n8n can tell you which input item produced which output item, and it's per item, all the way down the chain.

Records with many small keys are the expensive ones. Every key is a JavaScript object property with its own overhead, so 50,000 items with 40 fields each costs far more in heap than a 50,000 line CSV of the same content sitting on disk.

"Run Once for All Items" and "Run Once for Each Item" behave very differently

In All Items mode your code runs once and you reach the input with $input.all(). In Each Item mode your code runs once per item, and you work with $json or $input.item.

Per item mode is the slower of the two at high item counts because n8n pays the per invocation overhead thousands of times over. For anything past a few thousand items I write one loop in All Items mode and move on.

Note: per item mode is not a memory saver. n8n still has the complete input set in memory before the first invocation runs, so switching modes changes speed, not peak RAM.

Extra CPU cores don't make one Code node faster

JavaScript in n8n runs on a single thread. A synchronous for loop crunching 100,000 records blocks the event loop, which means webhook responses queue up, the editor UI gets sticky, and other executions on that instance wait their turn.

n8n added external task runners to deal with exactly this, moving Code node execution into a separate process from the main one. It landed as an opt in setting (N8N_RUNNERS_ENABLED) and newer releases have been shifting it toward the default, so check the n8n documentation for the behaviour in your version.

Where more cores do help: running several executions at the same time. Queue mode (EXECUTIONS_MODE=queue) with Redis and separate worker processes spreads concurrent executions across cores. N8N_CONCURRENCY_PRODUCTION_LIMIT caps how many production executions run at once on a regular instance, which is a blunt but effective way to stop ten heavy workflows firing together and taking the process down.

The Python option in the Code node is heavier than the JavaScript one

Python in the Code node runs through Pyodide, which is CPython compiled to WebAssembly and loaded inside the same JavaScript runtime. That adds startup cost and memory on top of your actual code, and you only get libraries Pyodide supports.

n8n's docs are upfront that this path is slower than JavaScript. If you're writing Python in a Code node because you prefer the syntax, rewrite it in JavaScript. If you need a specific Python library, keep the Python but move the heavy lifting out to a real Python service and call it over HTTP.

Measuring your own numbers takes about twenty minutes

Difficulty: beginner, as long as you have shell access to the host. Guessing at RAM requirements is how people end up paying for 8GB to run a workflow that needed 1.5GB and better filtering.

  1. Open a second terminal and start docker stats on the n8n container, or htop if you're running it directly. Watch the memory column, not the average.
  2. Trigger the workflow manually with a realistic payload. Five test items tell you nothing. Use the item count you actually hit in production, on the busiest day.
  3. Add a single line at the top of your Code node that logs the input size, for example console.log($input.all().length), and check it against what you expected. Item counts creeping up over months is the most common cause of a workflow that "suddenly" started crashing.
  4. Set the heap ceiling deliberately with NODE_OPTIONS=--max-old-space-size=2048 (in megabytes) so failures happen at a number you chose rather than wherever V8 decided the limit was. Predictable failure is easier to plan around.
  5. Turn on N8N_METRICS=true if you want ongoing visibility. It exposes a Prometheus endpoint at /metrics you can scrape and graph over time.

Warning: don't run this test on a production instance in working hours. A heap crash takes down the whole Node process, and every execution in flight at that moment dies with it. Some of those won't be safe to retry.

Timing note: memory usage keeps climbing until the execution finishes. If you watch for thirty seconds and see a flat line, you're looking at the wrong part of the run.

Cutting memory use comes down to moving less data, not writing cleverer code

These are ordered by how much difference they make, in my experience, with the smallest lever last.

Filter before the Code node, never inside it

Pulling 50,000 records and discarding 48,000 in your script means the full set was in memory anyway. Push the filter into the source: a WHERE clause, an API query parameter, a date range on the request.

The same logic applies to fields. Select the six columns you need rather than SELECT *. A Code node that's only grabbing a hostname from a link has no business receiving the full page HTML alongside it.

Stop cloning the input

const items = JSON.parse(JSON.stringify($input.all())) doubles your peak memory in one line. So does building a fresh output array while holding the input array, then holding both plus the return value.

Mutate in place where you can, and return the array you already built instead of copying it into a new shape. Avoid .map() chains three deep on large sets: each link in the chain is another full array in memory.

Batch with the Loop Over Items node

The Loop Over Items node (older workflows call it Split In Batches) sends items through in chunks you define. Set the batch size to something like 200 or 500 and your Code node works on that slice at a time.

This helps most when the expensive part is downstream, for example an HTTP request per item. It helps less if the source node still loaded everything before the loop began, so pair it with real source side filtering.

Push binary data off the heap

Binary data is the fastest way to blow up an n8n instance. n8n keeps it in memory by default. Set N8N_DEFAULT_BINARY_DATA_MODE=filesystem and it writes to disk instead, which trades RAM for storage and I/O.

Files still need somewhere to live and a sensible ceiling, so it's worth deciding on reasonable upload limits before you start moving PDFs and images through a Code node. Webhook payload size has its own cap through N8N_PAYLOAD_SIZE_MAX, which defaults to 16MB in the versions I've worked with.

Split the job across sub-workflows

Call a sub-workflow with the Execute Sub-workflow node and only the data you return comes back to the parent. Everything the child built along the way is free to be reclaimed once it finishes.

This is the strongest structural fix for a workflow that grew organically into thirty nodes. Give the child one job, return a small summary, keep the parent thin.

Trim what n8n saves after the run

Saved execution data doesn't consume heap during the run, but it does fill your database and slow the executions list to a crawl. EXECUTIONS_DATA_SAVE_ON_SUCCESS=none stops storing successful runs. EXECUTIONS_DATA_PRUNE=true with EXECUTIONS_DATA_MAX_AGE (in hours, 336 by default in recent builds, so 14 days) and EXECUTIONS_DATA_PRUNE_MAX_COUNT clears out the backlog.

Recent versions enable pruning out of the box. Confirm the defaults for your version rather than assuming.

Set an execution timeout

EXECUTIONS_TIMEOUT defaults to no limit, which means an accidental infinite loop in a Code node holds memory until you notice. Put a number on it, in seconds, generous enough for your slowest legitimate workflow.

Raise the heap last, not first

NODE_OPTIONS=--max-old-space-size=4096 buys headroom, and sometimes headroom is the honest answer for a genuinely large batch job. Do it after the steps above, because a bigger heap on an unfiltered workflow only moves the crash further into the future.

Never set it above what the machine has. If the heap limit exceeds physical RAM, the kernel's OOM killer steps in before V8 does, and that failure is uglier.

Sizing a server for n8n resource usage with heavy Code node workflows

For heavy Code node work I'd start at 2 vCPU and 4GB RAM and adjust from measurements. A single user with light workflows and no big payloads runs fine on 1 vCPU and 1GB. The gap between those two setups is almost entirely about how much data your scripts hold at once.

Add up everything sharing the box before you commit. Queue mode brings Redis. Anything past hobby use should be on PostgreSQL rather than SQLite.

Each worker process gets its own heap, so three workers at 1GB each need 3GB plus room for the main instance, the database and the OS.

Shared hosting is the wrong home for this. n8n needs a long running Node process, usually via Docker, and most shared plans won't give you that. If you're currently on one and weighing the move, the case for going from a shared plan to your own server is straightforward here: you need control over memory limits, environment variables and restarts.

Two operational details worth sorting on day one. Point a subdomain at the instance, since pointing a subdomain at a self-hosted app is cleaner for webhook URLs than juggling an IP and port. And check what your host's snapshots cover, because how far back daily backups reach decides whether a bad migration costs you an hour or a week of execution history.

Note: n8n Cloud sizes the resources for you, and you can't set NODE_OPTIONS or install npm packages for the Code node there. Cloud plans differ in what they allow, so check n8n's pricing page for the current limits rather than assuming your self-hosted tuning carries over.

What people get wrong about n8n memory use

Mix-up: the Code node is the memory hog.

Reality: the sandbox around your script is small. The items sitting in every node's output for the whole execution are what fill the heap, and a Code node in the middle of a data heavy chain is where the ceiling happens to get hit.

Mix-up: adding CPU cores will speed up a slow Code node.

Reality: one execution uses one thread for JavaScript, so a second core sits idle while your loop runs. Extra cores help you run more executions side by side, the same way a busy server slowing down every site on it is about contention rather than raw speed.

Mix-up: turning off execution data saving frees memory during the run.

Reality: it cuts database growth and post run write load. The in memory data still exists from the moment a node produces it until the execution ends.

Mix-up: the editor freezing means the server is out of memory.

Reality: opening an execution with 50,000 items renders that payload in your browser. That's your laptop's RAM, not the server's. Check docker stats before you resize anything.

The three failures you'll actually hit

Problem: the container restarts mid execution and the logs show exit code 137.

Fix: the kernel or Docker killed the process for exceeding its memory limit. Raise the container limit, or lower --max-old-space-size so V8 fails inside the process where you get a readable error instead of a silent kill.

Problem: JavaScript heap out of memory on a workflow that used to work.

Fix: the input grew. Log the item count, filter at the source, and move the heavy section into a sub-workflow. Raising the heap is the fallback, not the first move.

Problem: the workflow finishes but everything else on the instance stalls while it runs.

Fix: your Code node is blocking the event loop. Break the work into batches, turn on task runners so Code execution moves to a separate process, and cap concurrency with N8N_CONCURRENCY_PRODUCTION_LIMIT.

What this means for your setup

Start with a measurement, not a purchase. Watch memory during a realistic run, then apply source side filtering and the sub-workflow split. Most instances I've looked at were running two to three times the data they needed through the workflow, and the crash went away without touching the server.

Once you know your real peak, size for it with headroom for concurrent runs. Write the numbers down somewhere with your environment variables, since a plain record of your stack saves the next person from re-deriving your heap settings from scratch. Even a small Code node job like reducing messy URLs to a base host becomes an incident when it inherits 200,000 items from a node upstream.

If the answer is a bigger machine, a VPS you control is the only sensible platform for this, because you need Docker, environment variables and restart control.

Hostinger's VPS plans currently run up to 85% off with the code hHostCouponHub at checkout: start here to apply the discount. The discounted rate covers your first term, and renewals go up, so pick the term length knowing that.

Frequently asked questions

How much RAM does self-hosted n8n need for heavy Code node workflows?

Start at 4GB with 2 vCPU for data heavy Code node work, and 1GB is enough for light single user automations. Your real number depends on peak item count and payload size, so measure a full production run with docker stats before sizing up. Add memory separately for PostgreSQL, Redis and any worker processes.

Can I use npm packages inside the n8n Code node?

On self-hosted instances, yes, through the NODE_FUNCTION_ALLOW_EXTERNAL environment variable, with NODE_FUNCTION_ALLOW_BUILTIN for Node's built in modules. n8n's docs list both as self-hosted only, so Cloud users can't install packages. Every extra module you allow loads into the same process and adds to its memory footprint.

Why is Python slower than JavaScript in the Code node?

Python runs through Pyodide, a WebAssembly build of CPython loaded inside the JavaScript runtime, which adds startup time and memory before your code does anything. You're also limited to libraries Pyodide supports. Use JavaScript unless a specific Python library is the reason you're there.

Does splitting one big Code node into several smaller ones reduce memory use?

No, and it usually raises it. Each node's output is retained for the whole execution, so three Code nodes in a row mean three sets of items in memory instead of one. Use sub-workflows if you want intermediate data actually released.

Trim the data first, then buy the headroom you still need. If a VPS is the next step, the code hHostCouponHub takes up to 85% off Hostinger's plans on a first term: check the current pricing and pick a size that matches the peak you measured, not the crash you're trying to avoid.

This page contains affiliate links. If you purchase through the links on this page, we may earn a commission, at no extra cost to you.

Leave a Comment