FlowKit

n8n Task Runners: isolating and securing Code node execution

Published 1 August 2026 · 6 min read

Until recently, an n8n Code node ran directly inside the instance's main process: a bug or a malicious script pasted into a field could, in theory, reach everything that process could see — credentials in memory, other running executions, system resources. Since n8n 2.0, released in April 2026, that's no longer the default behavior: the Code node now runs in a separate Task Runner process, isolated from the main instance. This guide explains how that architecture works, how to configure it in self-hosted setups, and what actually changes for your AI workflows.

What default isolation changes

On an n8n instance that handles API credentials, receives publicly exposed webhooks, and runs code contributed by several collaborators, the Code node has historically been the most sensitive spot: it's the one place where arbitrary JavaScript (or Python) runs. With n8n 2.0, that code no longer executes inside the main process: it's delegated to a Task Runner, a separate process that, by default, has neither access to the instance's environment variables nor the ability to run system commands. This is a deliberate "secure by default" shift from the n8n team, not an option buried in advanced docs.

This principle isn't new in software security: privilege separation — isolating a risky component into its own process, with its own permissions, to limit the damage in case of compromise — is a model described as early as 2003 by Niels Provos, Markus Friedl, and Peter Honeyman in their reference paper presented at the 12th USENIX Security Symposium (“Preventing Privilege Escalation” — Google Scholar), which notably popularized this approach for OpenSSH. Task Runners apply the same logic to the Code node: a buggy script or a compromised dependency stays confined to the runner, without direct access to the rest of the instance.

The architecture: runner, broker, requester

Three components interact:

  • The task requester — the main n8n instance, which needs to execute a Code node and submits a request.
  • The task broker — a server built into n8n that receives requests and dispatches them to available runners over a websocket connection. It exposes a health check endpoint (/healthz) on port 5679 by default, configurable via N8N_RUNNERS_BROKER_PORT.
  • The task runner — the process that actually executes the JavaScript or Python code and returns the result.

This architecture deliberately echoes queue mode with Redis: in both cases, execution is offloaded from the main process to gain robustness. The difference is the goal — queue mode targets throughput and high availability, Task Runners primarily target the security isolation of one specific component, the Code node. The two mechanisms are independent and combine without conflict on the same instance.

Internal mode vs external mode

n8n offers two ways to run runners:

Internal mode

The n8n instance launches the runner itself as a child process and manages its lifecycle. The runner shares the same system user (uid/gid) as the main process. This is the simplest mode to enable, but isolation stays partial: n8n and the runner run with the same filesystem permissions. Official documentation advises against this mode in production.

External mode

A dedicated launcher — shipped in the official n8nio/runners image — starts and supervises the runner in its own container, independent of the n8n process. This is the recommended mode in production: on a Docker Compose deployment, it translates into a dedicated sidecar service.

services:
  n8n:
    image: n8nio/n8n:2.x
    environment:
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_BROKER_PORT=5679
      - N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
    ports:
      - "5678:5678"

  n8n-runner:
    image: n8nio/runners:latest
    environment:
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
      - N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}

Two details are easy to miss:

  • The shared token (N8N_RUNNERS_AUTH_TOKEN) must be identical on the main instance and on every runner: it authenticates the websocket connection to the broker, preventing a third-party process from connecting to it.
  • The broker port only listens on localhost by default. With several Docker Compose containers, it needs to accept external connections to the main n8n container — a detail that often trips up first deployments in external mode, with a runner that never manages to connect even though the configuration looks correct.

What isolation changes for your modules and variables

Two settings that used to live on the main n8n process must now be set on the runner instead:

  • N8N_RUNNERS_STDLIB_ALLOW — the runner-side equivalent of the old NODE_FUNCTION_ALLOW_BUILTIN: the list of allowed built-in Node.js modules inside the Code node.
  • NODE_FUNCTION_ALLOW_EXTERNAL (or its runner-side equivalent depending on the version) — for external npm modules, which must also be physically present in the runner container's node_modules, not just the main instance's.

We cover this configuration in detail, custom Docker image included, in our guide on installing npm modules in the Code node. The most common trap after migrating to n8n 2.0 stays the same: setting these variables on the n8n service instead of the n8n-runner service, which leaves the module unfound with no error message pointing at the real cause.

Another direct consequence of isolation: the instance's environment variables are no longer accessible from a Code node by default, not even via process.env. A workflow that used to read a configuration value that way needs to be adapted — usually by passing the value explicitly as node input, via an upstream Set node.

Migrating an existing instance to n8n 2.0

Before switching over, the recommended approach follows the same reflexes as a standard n8n update, plus one runner-specific step:

  1. Test ahead of time: on a pre-2.0 version, manually enable N8N_RUNNERS_ENABLED=true on a test environment to observe the behavior before it stops being optional.
  2. Inventory existing Code nodes that read environment variables directly, call external npm modules, or depend on system access — these are the most likely candidates to break after migration.
  3. Provision the runner sidecar (external mode) in your docker-compose.yml before switching to 2.0, rather than discovering the broker connection error in production.
  4. Re-run critical workflows after the upgrade, especially ones that make heavy use of the Code node — RAG chunking pipelines or custom scoring logic, for example.

Why it matters especially for AI workflows

Packs built around complex Code nodes — reshaping payloads before an LLM call, computing priority scores, parsing structured responses — are exactly the kind of workflow where Task Runner isolation changes the risk calculus. The document ingestion pipeline in the RAG Assistant Pack (€119) and the sorting workflows in the Inbox AI Pack (€79) run normally under n8n 2.0 with no changes, precisely because they already avoid unnecessary npm modules and direct system access — the same discipline that makes a Task Runner migration painless. For an instance handling sensitive or regulated data, the Compliance & Audit Pack (€149) relies on that same traceability logic: the smaller a Code node's access surface, the easier it is to document what it does — and what it can't do.

In summary

Task Runners are no longer an optional feature to enable: since n8n 2.0, they're the default execution mode for the Code node, built around three components (requester, broker, runner) and two deployment modes, of which only external mode is recommended in production. Switching over means moving certain environment variables from the main process to the runner, verifying network reachability of the broker between containers, and testing Code nodes that relied on direct system access. Once in place, it's a security gain that costs nothing in functionality for the vast majority of workflows — including every one of the FlowKit packs.

FAQ

Frequently asked questions

Are Task Runners mandatory since n8n 2.0?

Yes. Since version 2.0, released in April 2026, Task Runners are enabled by default and the N8N_RUNNERS_ENABLED variable is deprecated: it no longer needs to be set manually. Running the Code node directly inside the main n8n process is now a deprecated mode, best avoided on any instance that still receives updates.

Do you need to configure Task Runners on n8n Cloud?

No. On n8n Cloud, Code node isolation is managed behind the scenes by n8n: there is no broker to configure and no sidecar to deploy. The configuration described in this article only applies to self-hosted instances (Docker, VPS, Kubernetes).

What is the concrete difference between internal and external mode?

In internal mode, n8n itself launches the runner process as a child process, sharing the same system user: convenient for testing, but isolation stays partial since the runner and the main instance share the same permissions. In external mode, a separate component (the launcher, shipped in the n8nio/runners image) starts and supervises the runner in its own container, with its own lifecycle — this is the recommended mode in production.

How do I know a Code node is failing because of Task Runner isolation?

The typical symptom doesn't explicitly mention runners: a module not found, an environment variable missing inside the Code node, or a timeout on an execution that used to be instant. The right reflex is to check the runner container's logs (not the main n8n instance's logs) and confirm that N8N_RUNNERS_STDLIB_ALLOW or NODE_FUNCTION_ALLOW_EXTERNAL are set on the correct process.

Bundle FlowKit Complet

€269