This page explains the three OS-level processes n8n can run, how each starts up, and how they cooperate in a distributed topology. For the behavior of a single workflow execution once a process picks up a job, see Workflow Execution Lifecycle. For BullMQ queue internals and concurrency controls, see Distributed Execution and Scaling. For the environment variables and config classes that drive the decisions described here, see Configuration System. For task runner subprocess architecture, see Task Runners and Code Execution.
executions.mode (env: EXECUTIONS_MODE, default regular) is the primary architectural switch. It determines whether workflow executions run in-process or are distributed to separate worker processes.
| Mode | Value | Topology |
|---|---|---|
| Regular | regular | Single main process; all executions run in-process |
| Queue | queue | main enqueues jobs to Redis/BullMQ; separate worker processes execute them; optional dedicated webhook processes |
Queue mode requires a Redis server. The worker and webhook commands enforce queue mode and refuse to start otherwise.
Sources: packages/cli/src/commands/start.ts193-221 packages/cli/src/commands/worker.ts64-70 packages/cli/src/commands/webhook.ts44-57
n8n startEntry class: Start in packages/cli/src/commands/start.ts
The main process hosts the editor UI, REST API, webhook endpoints, active workflow management, and ā in regular mode ā workflow execution itself.
| Service / Class | Responsibility |
|---|---|
Server (extends AbstractServer) | Express HTTP + WebSocket server |
REST controllers (registered in server.ts) | All /rest/* endpoints |
Push | SSE or WebSocket live updates to browser |
ActiveWorkflowManager | Activates trigger and polling workflows |
WorkflowRunner | Routes executions in-process or to queue |
ActiveExecutions | In-memory map of running executions |
WaitTracker | Resumes paused / waiting executions |
ExecutionsPruningService | Prunes old execution records |
ScalingService | BullMQ queue setup (queue mode only) |
Publisher / Subscriber | Redis pub/sub (queue mode only) |
In queue mode, Start.initOrchestration() packages/cli/src/commands/start.ts280-309 instantiates Publisher, PubSubRegistry, and Subscriber, subscribing to the command, worker-response, and MCP relay channels.
n8n workerEntry class: Worker in packages/cli/src/commands/worker.ts
Workers consume BullMQ jobs and execute workflows. They serve no UI or REST API. Multiple workers can run in parallel, each with its own concurrency limit.
Concurrency: set via --concurrency flag (default 10) or N8N_CONCURRENCY_PRODUCTION_LIMIT. Warning is issued if concurrency is set below 5 packages/cli/src/commands/worker.ts151-163
| Service / Class | Responsibility |
|---|---|
ScalingService.setupWorker(concurrency) | Registers BullMQ job consumer |
JobProcessor | Executes individual workflow jobs |
WorkerServer (optional) | Exposes /health, /credentials, /metrics |
Publisher / Subscriber | Redis pub/sub (command channel from main) |
MessageEventBus | Worker-local event bus for log streaming |
WorkerServer is only started if at least one of health checks, credential overwrites endpoint, or metrics is enabled packages/cli/src/commands/worker.ts180-189
n8n webhookEntry class: Webhook in packages/cli/src/commands/webhook.ts server class WebhookServer
The webhook process is optional, queue-mode-only, and handles incoming production webhook HTTP requests. It enqueues executions via BullMQ rather than running them locally. When deployed, set N8N_DISABLE_PRODUCTION_WEBHOOKS_ON_MAIN_PROCESS=true on the main process to avoid double-registration.
Process Topology and Key Classes
Sources: packages/cli/src/commands/start.ts packages/cli/src/commands/worker.ts packages/cli/src/commands/webhook.ts packages/cli/src/server.ts packages/cli/src/workflow-runner.ts packages/cli/src/scaling/scaling.service.ts
All three command classes extend BaseCommand packages/cli/src/commands/base-command.ts Its init() method runs a shared initialization sequence before each command's own setup.
BaseCommand.init() steps packages/cli/src/commands/base-command.ts84-186:
ErrorReporter.init())SIGTERM / SIGINT handlersLoadNodesAndCredentials.init())DbConnection.init())DbConnection.migrate())needsCommunityPackages = true)TaskRunnerModule (if needsTaskRunner = true and runners are enabled)MessageEventBus, PostHogClient, TelemetryEventRelayneedsTaskRunner flag: Set to true on both Start and Worker packages/cli/src/commands/start.ts62 packages/cli/src/commands/worker.ts45 meaning both main and worker processes can manage task runner subprocesses.
InstanceSettings.instanceType is set to 'main', 'worker', or 'webhook' based on which command runs, and is used throughout shared services to branch behavior (e.g., ScalingService.registerListeners() packages/cli/src/scaling/scaling.service.ts297-304).
WorkflowRunner packages/cli/src/workflow-runner.ts is the central dispatch point for all workflow executions. Its run() method is called regardless of how execution was triggered (webhook, manual, scheduled, etc.).
Decision logic packages/cli/src/workflow-runner.ts172-176:
shouldEnqueue =
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS === 'true'
? executions.mode === 'queue'
: executions.mode === 'queue' && executionMode !== 'manual'
By default, manual and chat executions always run in-process even in queue mode.
WorkflowRunner.run() Decision Flow
runMainProcess() packages/cli/src/workflow-runner.ts217-376:
Workflow instance from data.workflowDataWorkflowExecuteAdditionalData.getBase() for additional contextWorkflowExecute and calls processRunExecutionData(workflow) or delegates to ManualExecutionService.runManually()executionTimeout > 0enqueueExecution() packages/cli/src/workflow-runner.ts378-545:
JobData objectScalingService.addJob(jobData, { priority }) ā priority 50 for realtime jobs, 100 for othersPCancelable<IRun> that resolves when the worker reports completion via job.finished()Sources: packages/cli/src/workflow-runner.ts139-213 packages/cli/src/workflow-runner.ts217-376 packages/cli/src/workflow-runner.ts378-545
ScalingService packages/cli/src/scaling/scaling.service.ts wraps a Bull queue backed by Redis. The queue is named bull (prefixed per queue.bull.prefix config).
Queue initialization:
| Method | Called by | Purpose |
|---|---|---|
setupQueue() L60-109 | Main, Worker, Webhook | Creates Bull queue with RedisClientService connections |
setupWorker(concurrency) L111-137 | Worker only | Registers JobProcessor.processJob() as the job handler |
JobData fields used by workers:
| Field | Type | Description |
|---|---|---|
workflowId | string | Workflow to execute |
executionId | string | Pre-registered execution ID |
loadStaticData | boolean | Whether to load workflow static data from DB |
pushRef | string | undefined | Browser push reference for live output |
streamingEnabled | boolean | Whether SSE streaming is active |
restartExecutionId | string | undefined | ID of execution being restarted |
Progress message kinds (sent via job.progress(msg) from worker to main) packages/cli/src/scaling/scaling.service.ts338-434:
kind | Direction | Purpose |
|---|---|---|
job-finished | Worker ā Main | Completion summary (success/failure, timing, last node) |
job-failed | Worker ā Main | Error details |
respond-to-webhook | Worker ā Main | Webhook response data |
send-chunk | Worker ā Main | SSE streaming chunk |
abort-job | Main ā Worker | Cancel a running job |
Queue Mode Job Lifecycle
Sources: packages/cli/src/scaling/scaling.service.ts60-137 packages/cli/src/scaling/scaling.service.ts226-248 packages/cli/src/scaling/scaling.service.ts338-434 packages/cli/src/scaling/job-processor.ts
Beyond BullMQ, processes communicate over dedicated Redis pub/sub channels managed by Publisher and Subscriber services.
Channels and their purposes:
| Channel | Direction | Purpose |
|---|---|---|
| Command channel | Main ā Workers / Webhooks | Send commands: stop execution, reload license, restart workflows |
| Worker response channel | Workers ā Main | Acknowledge commands, report worker status |
| MCP relay channel | Worker ā Main | Forward MCP tool responses in multi-main setups |
PubSubRegistry holds the mapping of channel messages to handler functions. It is initialized (or re-initialized) after modules load to ensure module-contributed handlers are registered packages/cli/src/commands/worker.ts122-123 packages/cli/src/commands/start.ts282-283
Sources: packages/cli/src/commands/start.ts280-309 packages/cli/src/commands/worker.ts141-149
Task runners are child processes that execute sandboxed user code (Code nodes, expression evaluation). They are started by TaskRunnerModule when needsTaskRunner = true and taskRunners.enabled = true packages/cli/src/commands/base-command.ts167-178
| Runner | Package | Protocol | Use case |
|---|---|---|---|
JsTaskRunner | @n8n/task-runner | WebSocket | JavaScript Code nodes (VM sandbox) |
PythonTaskRunner | @n8n/task-runner-python | Subprocess pipe | Python Code nodes |
Deployment mode (taskRunners.mode):
| Mode | Behavior |
|---|---|
internal (default) | n8n spawns and manages the runner subprocess |
external | Runner runs as a separate sidecar (e.g., a distinct Docker container) |
TaskBrokerService accepts WebSocket connections from runners and routes task requests to them. WorkflowRunner sends tasks via TaskRequester packages/cli/src/workflow-execute-additional-data.ts50
In queue mode with multiMainSetup.enabled = true (requires a license with MULTIPLE_MAIN_INSTANCES), multiple main processes can run for high availability.
Leader election: A Redis-backed advisory lock in MultiMainSetup designates exactly one main as the leader at any time. Only the leader runs:
Instance role properties on InstanceSettings:
| Property | Meaning |
|---|---|
isLeader | This instance currently holds the leader lock |
isMultiMain | Multi-main mode is configured and licensed |
isSingleMain | Queue mode but only one main (no multi-main) |
instanceType | 'main' | 'worker' | 'webhook' |
In single-main queue mode, markAsLeader() is called unconditionally at startup packages/cli/src/commands/start.ts307-308
Sources: packages/cli/src/commands/start.ts204-227 packages/cli/src/commands/start.ts304-308
All process types handle SIGTERM / SIGINT via BaseCommand.onTerminationSignal() packages/cli/src/commands/base-command.ts317-343 A forceShutdownTimer force-kills the process after generic.gracefulShutdownTimeout seconds (default 30) if graceful shutdown stalls.
Main process shutdown sequence packages/cli/src/commands/start.ts84-117:
ActiveWorkflowManagerWaitTrackern8n.stopMultiMainSetup (if enabled)Publisher / Subscriber (if queue mode)instance-stopped eventActiveExecutionsMessageEventBusWorker process shutdown (ScalingService.stopWorker()) packages/cli/src/scaling/scaling.service.ts183-197:
getRunningJobsCount() reaches zeroSources: packages/cli/src/commands/start.ts84-117 packages/cli/src/commands/base-command.ts317-343 packages/cli/src/scaling/scaling.service.ts163-197
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.