📁 last Posts

n8n Queue Mode with Redis & Workers: Complete Docker Setup

n8n Queue Mode with Redis & Workers: Complete Docker Setup (2026)

📥 Full docker-compose.yml + Queue Watchdog template included

Your n8n instance handles 20 workflows fine. Then you add a webhook-heavy lead pipeline, an AI agent that runs 3 minutes per execution, and a nightly batch sync — and suddenly everything queues up behind a single process. The UI freezes, webhooks time out, and you're restarting the container at 2 AM.

The Direct Answer

n8n queue mode puts a Redis queue between the main n8n process and multiple worker processes. The main process runs the UI, API, and all triggers; workers pull jobs from Redis and execute them in parallel. You enable it with one environment variable — EXECUTIONS_MODE=queue — on both main and workers, plus a PostgreSQL database (queue mode does not work with SQLite).

This guide is the complete production reference: migration from normal mode, the full docker-compose file (section 6), how to verify workers actually work (section 7), multi-VPS workers (section 8), and a free queue watchdog template (section 11).


1. What Runs Where (The Truth)

Most guides draw the architecture but never explain the division of labor. This table is the part that saves you hours of confusion:

ComponentRuns onWhat it does
UI / Editor Main process only The n8n web interface, API, CLI
All triggers Main process only Schedule, webhook, and polling triggers. Workers never run triggers — you never activate workflows on workers.
Workflow executions Workers Every execution is enqueued by main and claimed by a free worker
Redis Its own container The Bull job queue: wait, active, completed, failed job lists
PostgreSQL Its own container Workflows, executions, credentials — the shared state every worker reads

Why this matters: a trigger fires on main, main writes a job to Redis, a worker claims it and executes. The workflow itself — nodes, credentials, activation state — lives in Postgres, so any worker can run any workflow without local configuration. This is also why SQLite is impossible: it can't serve concurrent writers.

ℹ️
If a worker crashes, main keeps running and other workers keep pulling jobs. That isolation is the whole point — and our n8n Error Handling guide shows how to make the failure alert you instead of vanishing into logs.

2. When to Use It (And When NOT)

Queue mode is not "the pro setup" — it's a scaling decision with real costs: two extra services to maintain, back up, and monitor. Choose based on symptoms, not ambition:

Symptom you're seeingVerdict
UI is slow or unresponsive while workflows run✅ Strong signal for queue mode
Webhook providers report timeouts during heavy executions✅ Strong signal — plus the webhook fix in section 9
One heavy workflow delays all the others✅ Queue mode isolates it on a worker
You need zero-downtime updates✅ Rolling worker updates (section 12)
Under a few hundred executions/day, single workflow at a time❌ Stay in normal mode — SQLite simplicity wins
You're on n8n Cloud❌ Queue mode is a self-hosted concern — Cloud is managed

Cost of admission on a self-hosted VPS: Redis needs ~100–200 MB, Postgres ~200–500 MB, each worker ~300–500 MB plus heap. Plan for ~1 GB of extra RAM on top of your current footprint — the sizing table in our Out of Memory guide covers the full picture.


3. Migrating from Normal Mode (SQLite → Postgres)

This is the step every other guide skips — and it's where people lose their workflows. The safe path: export everything from the old instance, build the new stack, import, verify, switch.

1

Export workflows and credentials from the OLD instance

Use n8n's built-in CLI — it bulk-exports everything in one command:

# Export ALL workflows as JSON files
docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/export

# Export ALL credentials
docker compose exec n8n n8n export:credentials --all --output=/home/node/.n8n/export

# Copy the export folder out of the container
docker compose cp n8n:/home/node/.n8n/export ./n8n-export
2

Build the new stack with the SAME encryption key

Use the docker-compose from section 6, but set N8N_ENCRYPTION_KEY to the same key as your old instance. Credentials are encrypted with this key — importing with a different key means re-entering every credential by hand.

3

Import into the new instance

Copy the files in and import:

# Copy the export into the NEW container
docker compose cp ./n8n-export n8n:/home/node/.n8n/export

# Import workflows and credentials
docker compose exec n8n n8n import:workflow --input=/home/node/.n8n/export
docker compose exec n8n n8n import:credentials --input=/home/node/.n8n/export
4

Verify, activate, switch

Open the new UI and confirm every workflow appears with its credentials. Re-activate your workflows (activation state doesn't carry over). Test one webhook end-to-end. Only then point your domain at the new instance.

⚠️
Two traps: (1) OAuth credentials (Google, LinkedIn) may need a fresh authorization click after import — open each OAuth credential and reconnect if n8n asks. (2) Don't delete the old instance until the new one has run a full day of production traffic.

4. Prerequisites

  • A VPS or server with Docker + Docker Compose (2 GB RAM minimum; 4 GB comfortable).
  • Your n8n already running per our Self-Hosted Docker installation guide — or a fresh server.
  • A domain/subdomain (e.g., n8n.yourdomain.com) with a reverse proxy. Webhook URLs work the same as normal mode — see section 9 for timing.
  • About 45 minutes for a careful migration; 20 for a fresh install.

5. The .env File (Encryption Key!)

Two values power the whole stack. The second one causes 90% of queue mode headaches:

# .env — place next to docker-compose.yml

# Encryption key — MUST be identical on main AND every worker.
# Generate with: openssl rand -hex 24
N8N_ENCRYPTION_KEY=your-random-48-character-hex-key

# PostgreSQL password
POSTGRES_PASSWORD=change-me-to-something-long

# Optional: Redis password (leave commented for a private Docker network)
# REDIS_PASSWORD=change-me-too
⚠️
Critical: n8n encrypts all credentials with N8N_ENCRYPTION_KEY. If main and workers have different keys, workers can't decrypt credentials and every workflow fails — silently. One key, in .env, referenced by every container. Never change it later without exporting and re-importing credentials.

6. Full docker-compose.yml

This is the complete production stack: Postgres (state) + Redis (queue) + main (UI/API/triggers) + 2 workers (execution). It includes the production hardening most guides omit: stalled-job detection, completed-job cleanup, graceful shutdown, and health checks.

version: '3.8'

volumes:
  n8n_data:
  postgres_data:
  redis_data:

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U n8n']
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    # Remove the password args if Redis only listens on the private Docker network
    command: ['redis-server', '--appendonly', 'yes', '--requirepass', '${REDIS_PASSWORD:-defaultpass}']
    volumes:
      - redis_data:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - '5678:5678'
    environment:
      # Core
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=Africa/Tunis
      - WEBHOOK_URL=https://n8n.yourdomain.com
      # Database (required for queue mode)
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      # Queue mode
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD:-defaultpass}
      - QUEUE_BULL_PREFIX=n8n-queue
      - QUEUE_HEALTH_CHECK_ACTIVE=true
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    command: worker
    environment:
      # Core — encryption key MUST match the main process
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=Africa/Tunis
      # Database — same shared Postgres
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      # Queue mode — same Redis, password, and prefix as main
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD:-defaultpass}
      - QUEUE_BULL_PREFIX=n8n-queue
      # Worker tuning
      - QUEUE_WORKER_CONCURRENCY=10
      # Stalled-job recovery: re-queue jobs whose worker died mid-execution
      - QUEUE_WORKER_STALLED_INTERVAL=60000
      - QUEUE_WORKER_MAX_STALLED_COUNT=3
      # Finish running jobs before shutting down (updates, scale-down)
      - N8N_GRACEFUL_SHUTDOWN_TIMEOUT=30
      # Keep Redis memory in check
      - QUEUE_BULL_JOB_OPTIONS_REMOVE_ON_COMPLETE=1000
      - QUEUE_BULL_JOB_OPTIONS_REMOVE_ON_FAIL=1000
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      n8n:
        condition: service_started

Launch it

# From the folder containing docker-compose.yml and .env
docker compose up -d

# Then create 2 worker replicas
docker compose up -d --scale n8n-worker=2

Notes on this file

  • Pin your n8n image version (e.g., docker.n8n.io/n8nio/n8n:1.120.0) instead of floating on latest — a bad release should never take your production stack down overnight. Postgres and Redis are pinned to major versions above.
  • QUEUE_BULL_PREFIX isolates your queue inside Redis. Running two n8n instances (e.g., n8n-prod and n8n-test) against one Redis server is fine — as long as each uses a different prefix, jobs never mix.
  • Workers don't need port 5678 — only main exposes it. Workers reach Redis and Postgres over the Docker network.
  • QUEUE_WORKER_STALLED_INTERVAL (ms): how often n8n checks for jobs whose worker died mid-execution. Paired with QUEUE_WORKER_MAX_STALLED_COUNT (how many times a stalled job is re-queued before failing), stuck jobs recover automatically. Important: a workflow that legitimately runs longer than the stalled interval can be falsely flagged as stalled — if you run long AI workflows, raise the interval (e.g., 120000).
  • Same n8n_data volume on workers — intentional: workers need the same .n8n folder for files and community nodes (single-host setups only; see section 8 for multi-VPS).
  • Redis password: the snippet uses ${REDIS_PASSWORD:-defaultpass} so it runs out of the box. On a private Docker network you can drop the --requirepass args and the QUEUE_BULL_REDIS_PASSWORD lines entirely.
  • Newer n8n versions also accept QUEUE_BULL_CONCURRENCY as the modern name for QUEUE_WORKER_CONCURRENCY — either works; don't set both.

7. Verify Workers Actually Work (No Guide Covers This)

Everything can look healthy while workers silently do nothing. Run these five checks after every setup or change:

1

All containers healthy

docker compose ps — Postgres, Redis, main, and workers all healthy/Up. A worker stuck in Restarting means a config error (usually Redis or DB connection).

2

Workers registered in the UI

In queue mode, n8n's Settings shows the registered workers and their status. If a worker doesn't appear, it's not connected to the same Redis — check its QUEUE_BULL_* variables (error #3 in section 10).

3

Run a test workflow and watch it complete

Create a tiny workflow (Schedule or Manual trigger → NoOp), execute it, and confirm it finishes with success. Then check the worker logs to see it was claimed:

docker logs n8n-worker-1 --tail 50
4

Watch a job move through Redis in real time

Trigger the test workflow while MONITOR runs — you'll see the job claimed by a worker:

docker compose exec redis redis-cli MONITOR
# Ctrl+C to stop
5

Check the queue depth is zero after the run

The wait list holds jobs no worker has claimed yet. After a healthy run it should be back to zero:

docker compose exec redis redis-cli LLEN n8n-queue:wait
💡
The most common "workers do nothing" cause is EXECUTIONS_MODE missing on the workers. Main enqueues jobs, but no one is listening. Step 2 catches it in ten seconds.

8. Scaling: Replicas, Concurrency, and Multi-VPS Workers

Two levers on one machine:

LeverWhat it doesHow
Worker replicas More parallel processes pulling from Redis docker compose up -d --scale n8n-worker=4
QUEUE_WORKER_CONCURRENCY More simultaneous jobs per worker (default 10) Set to 20 and restart workers — lower it to 3–5 for memory-heavy workflows

8.1 Multi-VPS workers (distributed)

When one VPS can't hold your load, run workers on a second server. They're just Docker containers pointed at the same Redis and Postgres over the network:

# On VPS #2 — a worker-only compose file
services:
  n8n-worker:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    command: worker
    environment:
      # Same key as VPS #1 — non-negotiable
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      # Point at VPS #1's services (private IP preferred)
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=10.0.0.1
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=10.0.0.1
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
      - QUEUE_BULL_PREFIX=n8n-queue
      - QUEUE_WORKER_CONCURRENCY=10
    volumes:
      - worker_data:/home/node/.n8n
  • Networking: don't expose Redis or Postgres to the public internet. Use a private network — Tailscale or WireGuard between the VPSs is the clean way, or your provider's private VLAN (the 10.0.0.1 above).
  • Community nodes: each worker needs the community nodes your workflows use installed on its own volume — a shared n8n_data volume doesn't cross VPS boundaries.
  • Latency: Redis and DB round-trips across the internet add execution overhead. Private networks keep this minimal; two VPSs in the same region/datacenter is the sane default.
  • Sizing rule of thumb: start with 2 workers × 10 concurrency = 20 parallel executions. Workers pinned at 100% CPU with a growing queue → scale up. Workers idling at 20% → scale down (they finish running jobs first, thanks to N8N_GRACEFUL_SHUTDOWN_TIMEOUT).
✅
This is exactly how we run TriggerWorkflow.com — the Website Downtime Checker polls every few minutes while long AI workflows run on other workers, and neither blocks the other.

9. Webhooks in Queue Mode (The Timing Trap)

In normal mode, a webhook execution runs locally and responds fast. In queue mode, the main process receives the request but the execution waits for a worker — so the HTTP response is delayed by your queue latency. Stripe, GitHub, and Shopify time out after ~10–30 seconds, then retry — causing duplicate processing.

The fix is two settings on your Webhook node:

  • Response Mode: Immediately — main replies 200 the moment it enqueues the job. The provider stops retrying.
  • Respond to Webhook node — when the workflow needs to send a custom body or status, place this node where the response should be built and configure the Webhook node to respond from it.
⚠️
Trade-off: "Immediately" means the provider never learns if the execution actually succeeded. For payment webhooks, keep the default response and add idempotency instead (deduplicate by webhook ID in Redis) — our Error Handling guide covers the full idempotency pattern.

10. The 9 Most Common Errors & Fixes

#Error / SymptomCauseFix
1 Workers never pick up jobs; queue stays full EXECUTIONS_MODE=queue set on main but missing on workers Add it to every worker container and restart them
2 Every workflow fails with a credential decryption error N8N_ENCRYPTION_KEY differs between main and workers One identical key everywhere; re-enter affected credentials once
3 connect ECONNREFUSED 127.0.0.1:6379 Pointing Redis at localhost instead of the Docker service name Use QUEUE_BULL_REDIS_HOST=redis (the service name), not 127.0.0.1
4 Worker restarts in a loop after enabling a Redis password Redis requires auth but workers don't send it Set QUEUE_BULL_REDIS_PASSWORD on main AND every worker
5 Executions stuck in "running" forever, or error job stalled more than maxStalledCount A worker crashed mid-execution — OR a workflow legitimately ran longer than the stalled interval Redis persistence (--appendonly yes), N8N_GRACEFUL_SHUTDOWN_TIMEOUT=30, stalled-interval settings (section 6)
6 Redis memory keeps growing until OOM Completed/failed job metadata never removed QUEUE_BULL_JOB_OPTIONS_REMOVE_ON_COMPLETE=1000 + REMOVE_ON_FAIL=1000, and a Redis maxmemory with allkeys-lru
7 Webhook providers report timeouts after switching to queue mode Webhook responses now wait for the worker to finish the execution Webhook Response Mode: Immediately + Respond to Webhook node (section 9)
8 Jobs vanish or mix between two n8n instances Both instances share Redis with the same QUEUE_BULL_PREFIX Unique prefix per instance (e.g., n8n-prod, n8n-test)
9 Multi-VPS workers can't connect to Redis/Postgres Firewall or public exposure — the ports aren't reachable privately Use Tailscale/WireGuard or the provider's private VLAN; never expose 6379/5432 publicly (section 8.1)
ℹ️
Errors 2, 5, and 7 are the silent killers — they fail without obvious logs. Route every worker failure through an Error Trigger workflow with severity classification: the Error Handling guide gives you the complete setup.

11. Free Template: The Queue Watchdog

Monitoring doesn't stop at /healthz. This workflow probes the actual queue depth every 5 minutes (Redis LLEN on the wait list) and alerts you on Telegram when jobs are backing up — before your webhook providers start complaining.

{
  "name": "n8n Queue Watchdog",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "id": "schedule",
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [250, 300]
    },
    {
      "parameters": {
        "operation": "listLength",
        "key": "n8n-queue:wait"
      },
      "id": "redis-depth",
      "name": "Queue Depth (LLEN)",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [450, 300],
      "credentials": {
        "redis": {
          "id": "YOUR_REDIS_CREDENTIAL_ID"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const depth = $input.item.json.length;\nconst threshold = 20;\nconst now = Date.now();\nconst lastAlert = $workflow.staticData.lastAlert || 0;\nconst notify = depth > threshold && (now - lastAlert > 900000);\nif (notify) { $workflow.staticData.lastAlert = now; }\nreturn [{ json: { depth, threshold, notify, timestamp: new Date().toISOString() } }];"
      },
      "id": "evaluate",
      "name": "Evaluate Depth",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [650, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.notify }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "if-alert",
      "name": "Backlog?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [850, 300]
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "=🚦 n8n QUEUE BACKLOG\n\nWaiting jobs: {{ $json.depth }}\nThreshold: {{ $json.threshold }}\nTime: {{ $json.timestamp }}\n\nWorkers are falling behind — check worker health or scale up."
      },
      "id": "telegram",
      "name": "Telegram Alert",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [1050, 200],
      "credentials": {
        "telegramApi": {
          "id": "YOUR_TELEGRAM_CREDENTIAL_ID"
        }
      }
    },
    {
      "parameters": {},
      "id": "noop-ok",
      "name": "Queue Healthy",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [1050, 400]
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Queue Depth (LLEN)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Queue Depth (LLEN)": {
      "main": [
        [
          {
            "node": "Evaluate Depth",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Depth": {
      "main": [
        [
          {
            "node": "Backlog?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Backlog?": {
      "main": [
        [
          {
            "node": "Telegram Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Queue Healthy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  }
}
  • Redis credential: create one Redis credential in n8n pointing at redis (or the private IP for multi-VPS) on port 6379 with your password.
  • The key n8n-queue:wait is the waiting-jobs list — change the prefix if you customized QUEUE_BULL_PREFIX.
  • Threshold 20: tune it. 20 waiting jobs is nothing at 100 executions/minute; it's an emergency at 2/minute. Alert once per 15 minutes max (cooldown built in).
  • Also monitor /healthz for the main process with UptimeRobot or our Website Downtime Checker template — watchdog + health checks cover both sides.

12. Zero-Downtime Updates

Queue mode's best operational gift: you can update n8n without downtime. Order matters — workers first, main last:

# 1. Pull the new (pinned) image
docker compose pull

# 2. Restart workers one by one — running jobs finish first
#    (graceful shutdown via N8N_GRACEFUL_SHUTDOWN_TIMEOUT)
docker compose up -d --no-deps --scale n8n-worker=0 n8n-worker
docker compose up -d --no-deps --scale n8n-worker=2 n8n-worker

# 3. Update the main process last
docker compose up -d --no-deps n8n
💡
Running executions finish on old workers while new executions go to updated ones. This only works when you pin versions — with latest, every restart is a surprise.

13. Frequently Asked Questions

Does n8n queue mode work with SQLite?

No. Queue mode requires PostgreSQL. SQLite can't handle concurrent writes from multiple workers, so n8n won't run queue mode without a Postgres database. Section 3 covers the migration path.

Do I need Redis if I only run one n8n instance?

No. A single instance in normal mode works perfectly without Redis. Queue mode only pays off when you need multiple workers, high concurrency, or isolation between the main process and heavy workflows.

Do workflow triggers run on the workers in queue mode?

No. All triggers — schedule, webhook, and polling — run on the main process only. Workers only execute the jobs main enqueues. You never need to activate workflows on workers.

Why are my workers not picking up jobs?

The #1 cause is EXECUTIONS_MODE=queue set on the main process but missing from the worker containers. Both must have it. Then verify both use the same Redis host, port, password, and QUEUE_BULL_PREFIX — section 7 gives you the 5-step verification.

Why do workflows fail with a credential decryption error after adding workers?

Your N8N_ENCRYPTION_KEY differs between main and workers. Credentials are encrypted with this key, so it must be identical everywhere. Fix the key, then re-enter the affected credentials once.

Can webhooks still work in queue mode?

Yes. Main receives webhook requests and enqueues them for a worker. But the response waits for the execution to finish — under load, set the Webhook node's Response Mode: Immediately and reply with a Respond to Webhook node (section 9).

How many workers should I run?

Start with 2 workers. Each handles up to QUEUE_WORKER_CONCURRENCY jobs in parallel (default 10). Scale up when CPU saturates or the Redis queue grows at peak hours; scale down when workers idle.

Can I run workers on a different server than the main process?

Yes. Workers on a second VPS just need to reach the same Redis and PostgreSQL over the network — ideally through Tailscale or WireGuard — and use the same N8N_ENCRYPTION_KEY. Install community nodes on each worker. Full compose file in section 8.1.

How do I update n8n without downtime in queue mode?

Update workers first: pull the new image, restart workers one by one, and keep the main process running. Then update main last. Running executions finish on old workers while new ones go to updated workers (section 12).

What's the difference between queue mode and n8n task runners?

Queue mode distributes entire workflow executions across worker processes. Task runners (the newer N8N_RUNNERS_* feature) offload only JavaScript/Python Code node execution to separate runner processes. They solve different problems and can be combined — a worker can hand its Code nodes to runners.


Sources & References

Explore More Guides

Ahmed Ayari — TriggerWorkflow I run this exact queue mode stack in production on TriggerWorkflow.com — two workers handling everything from uptime checks to long AI agent executions, with the watchdog from section 11 watching the backlog. Every error in section 10 is one I've hit, fixed, and documented. Read more guides by Mr.Ayari
Comments