n8n high availability in the free Community edition means one main process plus as many workers as you want, all talking through Redis and PostgreSQL, with the main set up to come back within seconds when it dies. That is queue mode, and it needs no licence; two mains standing in for each other (n8n calls it multi-main) is an Enterprise feature listed as "Contact Sales". We built the Community version on a 2 vCPU, 3 GiB test machine on 28 October 2026 with n8n 2.45.5, then killed a worker mid-burst, restarted the main mid-burst and stopped Redis. A dead worker cost 10 of 200 executions and was back in a second; a main restart took it offline for about 14 seconds, in which 99 of 400 webhook calls aimed at it failed, while calls aimed at a webhook processor all succeeded.
This is the scaling chapter for people who already run n8n on one server; if you are starting from zero, read how to self-host n8n with Docker first, and see our n8n VPS requirements load test for single-instance numbers.
Key facts, checked on 28 October 2026:
- In queue mode the main instance "handles timers and webhook calls" and passes execution IDs to Redis; workers run the workflows. SQLite "isn't supported"; default worker concurrency is 10 (n8n docs: queue mode).
- Queue mode is included in the Community edition; "Multi-main mode" needs a paid edition (n8n docs: Community edition features).
- Supported databases: PostgreSQL 16, 17 and 18 (n8n docs). n8n 2.0 removed
QUEUE_WORKER_MAX_STALLED_COUNT"along with Bull's automatic retry of stalled jobs" (n8n 2.0 breaking changes). - n8n 2.45.5 was published on 28 October 2026 (GitHub release); the docs recommend pinning an exact version.
- Redis "is designed to be accessed by trusted clients inside trusted environments" (Redis security); Docker's published ports skip ufw, "effectively ignoring your firewall configuration" (Docker docs).
How n8n high availability works in the Community edition, and what it cannot do
A normal n8n install is one process that shows the editor, listens for webhooks, fires timers and runs the workflows. Queue mode splits that into roles. The main keeps the editor, the timers and pollers (triggers that ask a service every minute for new data) and, by default, the webhook listener; it creates an execution record, puts its ID on a queue in Redis (an in-memory store) and runs nothing itself. Workers take IDs from Redis, load the workflow from PostgreSQL, run it and write the result; you can run any number on any server that reaches both. Optional webhook processors only accept webhook calls and queue them, so a flood never touches the main.
Here is the honest part. Everything except the main can be duplicated for free. The main cannot: while it is down the editor is down, timers do not fire and webhooks pointed at it fail. Several mains with an elected leader is multi-main, which needs an Enterprise licence and a load balancer with sticky sessions (n8n docs). So self-hosted n8n HA without a licence is four things done well: a main that restarts within seconds and is watched from outside; webhooks received by a processor so a main restart loses none; workers on two servers; and Postgres and Redis sized, backed up and never reachable from the internet. That covers the failures you will actually see, not the main server dying at 3 a.m. with nobody awake.
What we broke on purpose, and what happened
Everything ran inside one Debian 13 machine limited to 2 vCPU and 3 GiB: Docker Compose, the images n8nio/n8n:2.45.5, postgres:17 and redis:7-alpine, two workers at concurrency 10, and a three-node workflow (Webhook responding immediately, Wait 1 second, Edit Fields). A burst is 200 GET requests, 20 at a time.
| Test | What happened | What it means |
|---|---|---|
| 200 calls at the main | All answered 200 in 9.4 s (median 0.76 s, slowest 2.26 s). Worker 1 ran 103, worker 2 ran 97. All succeeded. | Redis spreads work evenly with no setting. The main used 45% of a core just accepting webhooks. |
| 200 calls at the webhook processor | All answered 200. Main CPU 2%, processor 52% of a core. | A processor takes the whole incoming load off the main. |
| Worker killed with SIGKILL 3 s into a burst | Docker restarted it within a second. 190 of 200 succeeded; the dead worker had started 11 and finished 3. Two were marked "crashed" at once, eight more within five minutes. None re-ran. | A dead worker loses what it had in hand. |
docker compose restart n8n-main, calls at the processor | All 200 answered and succeeded. The main was unreachable for about 14 s and peaked at 608 MiB while starting. | With a processor, a main restart is invisible to senders. Only timers and the editor pause. |
| Same restart, 400 calls at the main | 301 answered 200, 99 got a connection error. | Without a processor every main restart drops webhooks for the whole window. |
| Redis stopped for 25 s | A webhook call hung 13.9 s, then failed. Every n8n process logged "Exiting process due to Redis connection error" after 10 s; Docker restarted them in a loop, and 40 s after Redis returned all were healthy. | Redis takes the whole system down. It used 16 MiB, but it must be watched. |
docker kill on a worker | Stayed "Exited (137)": Docker treats kill and stop as manual and ignores the restart policy (Docker docs). | Test crashes by killing the process inside the container. |
After 1,905 executions the Postgres database was 22 MB, and the three images take 2.5 GB of disk. Memory figures are in the plan section below.
Step 1: one Compose file for Postgres, Redis, main, workers and a webhook processor
You need Docker Engine with the Compose plugin and a reverse proxy with HTTPS; the n8n setup guide covers both. Below is the file we ran (the lab had no domain, so there N8N_HOST was 127.0.0.1:5678 over http). Secrets go in a .env file. The encryption key is the one line you must never lose or change: every credential in Postgres is encrypted with it, and every worker and processor needs the same key to read them (n8n docs).
# as root on server A
mkdir -p /opt/n8n && cd /opt/n8n
printf "POSTGRES_PASSWORD=%s\nREDIS_PASSWORD=%s\nN8N_ENCRYPTION_KEY=%s\nN8N_HOST=n8n.example.com\nN8N_PROTOCOL=https\n" \
$(openssl rand -hex 24) $(openssl rand -hex 24) $(openssl rand -hex 32) > .env
chmod 600 .env && cat .env
POSTGRES_PASSWORD=2cf2b189...
REDIS_PASSWORD=e95f4e59...
N8N_ENCRYPTION_KEY=4149b1d3...
N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
Copy the key somewhere safe off the server now. The x-n8n-env block is written once and merged into main, workers and processor, so they can never disagree about the database or the key:
# as root on server A: /opt/n8n/compose.yaml
x-n8n-env: &n8n-env
EXECUTIONS_MODE: queue
QUEUE_BULL_REDIS_HOST: redis
QUEUE_BULL_REDIS_PORT: "6379"
QUEUE_BULL_REDIS_PASSWORD: ${REDIS_PASSWORD}
QUEUE_HEALTH_CHECK_ACTIVE: "true"
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: "5432"
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_EDITOR_BASE_URL: ${N8N_PROTOCOL}://${N8N_HOST}/
N8N_WEBHOOK_URL: ${N8N_PROTOCOL}://${N8N_HOST}/
N8N_PROXY_HOPS: "1"
GENERIC_TIMEZONE: Europe/Amsterdam
TZ: Europe/Amsterdam
N8N_DIAGNOSTICS_ENABLED: "false"
N8N_GRACEFUL_SHUTDOWN_TIMEOUT: "60"
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: "true"
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: "168"
services:
postgres:
image: postgres:17
restart: always
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: always
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a $$REDIS_PASSWORD ping | grep -q PONG"]
interval: 5s
timeout: 5s
retries: 10
n8n-main:
image: n8nio/n8n:2.45.5
restart: always
environment:
<<: *n8n-env
ports:
- "127.0.0.1:5678:5678"
volumes:
- n8n_main:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz/readiness >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 60s
n8n-worker:
image: n8nio/n8n:2.45.5
restart: always
command: ["worker", "--concurrency=10"]
environment:
<<: *n8n-env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz/readiness >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 60s
n8n-webhook:
image: n8nio/n8n:2.45.5
restart: always
command: ["webhook"]
environment:
<<: *n8n-env
ports:
- "127.0.0.1:5680:5678"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz/readiness >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 60s
volumes:
pg_data:
redis_data:
n8n_main:
The lines that are not obvious:
EXECUTIONS_MODE: queueon every n8n process. A worker without it runs nothing; a main without it runs workflows itself and ignores the workers.QUEUE_HEALTH_CHECK_ACTIVE: "true"turns on/healthzand/healthz/readinesson workers, off by default (n8n monitoring docs). Readiness answers 200 only with Postgres and Redis connected.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: "true"sends "Test workflow" runs to the workers too; the default isfalse(queue-mode variables).EXECUTIONS_DATA_MAX_AGE: "168"prunes history after 7 days instead of 14.- Redis
noevictionreturns an error when full instead of silently dropping queue entries;appendonly yeskeeps queued jobs across a Redis restart.$$is Compose's way of writing a literal$. Published ports are bound to127.0.0.1; Postgres and Redis publish nothing.
Two things we left out: binary data stays in the database in queue mode, because filesystem mode is not supported there and S3 needs a paid licence (binary data docs); and the built-in Code node runner is logged as deprecated in favour of a separate n8nio/runners container per process (task runner docs), which we did not test.
Step 2: start it, make a test workflow, fire 200 webhooks
# as root on server A
cd /opt/n8n && docker compose up -d --scale n8n-worker=2
sleep 60 && docker compose ps --format "table {{.Name}}\t{{.Service}}\t{{.Status}}"
NAME SERVICE STATUS
n8n-n8n-main-1 n8n-main Up About a minute (healthy)
n8n-n8n-webhook-1 n8n-webhook Up About a minute (healthy)
n8n-n8n-worker-1 n8n-worker Up About a minute (healthy)
n8n-n8n-worker-2 n8n-worker Up About a minute (healthy)
n8n-postgres-1 postgres Up About a minute (healthy)
n8n-redis-1 redis Up About a minute (healthy)
--scale n8n-worker=2 starts two workers; change the number and rerun any time. The worker log confirms queue mode:
docker compose logs n8n-worker --no-log-prefix | grep -E "Version|Concurrency|Queue"
* Version: 2.45.5
* Concurrency: 10
* Queue: jobs
Open the editor through your proxy, create the owner, and build the three nodes (Webhook trigger with path burst, method GET, "Respond: Immediately"; Wait 1 second; Edit Fields). Activate it, then fire the burst with your domain:
# as any user on server A
time (seq 200 | xargs -P 20 -I{} curl -s -o /dev/null -m 10 -w "%{http_code}\n" \
https://n8n.example.com/webhook/burst | sort | uniq -c)
sleep 25
docker compose logs --since 2m n8n-worker | grep "finished execution" | awk '{print $1}' | sort | uniq -c
200 200
real 0m9.431s
103 n8n-worker-1
97 n8n-worker-2
If the webhook answers but nothing runs, a worker is missing EXECUTIONS_MODE, has a different encryption key or cannot reach Redis; its log says which.
Step 3: make the single main process restart fast and be watched
Docker does the restart. restart: always brought the killed worker back within a second and every n8n process back after the Redis outage. Docker counts a start as successful after 10 seconds and leaves a container that dies faster alone (Docker docs); do not add systemd units on top.
A webhook processor keeps accepting during the restart. Route /webhook/* and /webhook-waiting/* in your reverse proxy to port 5680 and everything else, including /webhook-test/*, to 5678 (n8n docs), then set N8N_DISABLE_PRODUCTION_MAIN_PROCESS: "true" on the main. Many webhook senders retry on a connection error, but not all; check the ones you depend on, and put risky steps behind an Error Workflow, because executions a dying worker had in hand end as crashed.
Watch from another server. Run an Uptime Kuma monitor on https://n8n.example.com/healthz and on the webhook path from a different machine, with Telegram alerts; our Uptime Kuma and Grafana guide shows how. /healthz/readiness also confirms Postgres is reachable (n8n monitoring docs). When upgrading, change the pinned tag and restart main before the workers.
Step 4: add a worker on a second VPS over WireGuard
One kernel panic still stops a single server, so the Community step up is a second server that runs only workers. Workers need Redis on 6379 and Postgres on 5432 from server A, and neither may ever be reachable from the internet, so the servers talk over a tunnel. Our WireGuard VPN server page covers the install; you want server A at 10.8.0.1, server B at 10.8.0.2, and ping 10.8.0.1 working from B. Then publish Redis and Postgres on that address only:
# as root on server A
cd /opt/n8n && echo "PRIVATE_IP=10.8.0.1" >> .env
# in compose.yaml, under redis: ports: ["${PRIVATE_IP}:6379:6379"]
# in compose.yaml, under postgres: ports: ["${PRIVATE_IP}:5432:5432"]
docker compose config -q && docker compose up -d
ss -tlnp | grep -E "6379|5432|5678|5680"
LISTEN 0 4096 127.0.0.1:5680 users:(("docker-proxy",...))
LISTEN 0 4096 127.0.0.1:5678 users:(("docker-proxy",...))
LISTEN 0 4096 10.8.0.1:6379 users:(("docker-proxy",...))
LISTEN 0 4096 10.8.0.1:5432 users:(("docker-proxy",...))
This is the most important firewall line in the setup. Docker's published ports skip ufw, so a plain ports: ["6379:6379"] puts Redis on the public internet whatever ufw says; binding to the WireGuard address means the port exists only on the tunnel. On the lab machine, with its private address standing in for 10.8.0.1, redis-cli -h 10.8.0.1 ping without a password got NOAUTH Authentication required. and redis-cli -h 127.0.0.1 ping got Connection refused.
Server B gets Docker, the same n8n version and a Compose file with only the worker: the x-n8n-env block from server A with QUEUE_BULL_REDIS_HOST and DB_POSTGRESDB_HOST changed to 10.8.0.1, plus a copy of .env, because the encryption key must be identical.
# as root on server B: /opt/n8n-worker/compose.yaml (x-n8n-env block above, two hosts changed)
services:
n8n-worker:
image: n8nio/n8n:2.45.5
restart: always
command: ["worker", "--concurrency=10"]
environment:
<<: *n8n-env
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz/readiness >/dev/null || exit 1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 60s
# as root on server B
cd /opt/n8n-worker && chmod 600 .env && docker compose up -d && sleep 50
docker compose ps --format "{{.Name}} {{.Status}}"
docker compose logs --no-log-prefix | grep -E "Version|Concurrency|Registered"
n8n-worker-n8n-worker-1 Up 50 seconds (healthy)
* Version: 2.45.5
* Concurrency: 10
Registered runner "JS Task Runner" (9diag9WVfL5qPgmaToQWB)
We ran exactly this worker as a second Compose project against Redis and Postgres on the private address, then fired 200 webhooks at the main: the local worker ran 103, the "second server" worker 97, all succeeded. The WireGuard tunnel itself did not fit in one 3 GiB lab machine, so confirm the 10.8.0.x addresses with ping first. Keep both servers in the same city: every execution makes several round trips to Postgres and Redis.
Redis, Postgres and backups: the parts that must not fail
Every n8n process gives up after 10 seconds without Redis (QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD) and exits; Docker restarts them until Redis is back, but for the whole outage no webhook is accepted and no timer fires. So Redis and Postgres belong on the server with dedicated CPU and the most RAM. Tuning is in our PostgreSQL, MySQL and Redis on a VPS guide, sizing in how to size a VDS for a production database. Postgres holds the real state; dump it daily and get the dump off the server:
# as root on server A, from cron, daily
cd /opt/n8n && docker compose exec -T postgres pg_dump -U n8n -d n8n -Fc > /var/backups/n8n-$(date +%F).dump
Push the dumps and the .env file elsewhere with restic, as in our off-site backups with restic and MinIO guide; a dump without the encryption key is a list of credentials you can never open. Our servers in Prishtina include weekly backups of the whole server with one-click restore from the client area (a daily add-on exists); Amsterdam and Dublin servers do not include backups, so there the restic job is not optional.
Which RS Computers plan for which n8n layout
Measured at idle: 358 MiB for the main (608 MiB while starting), 254 MiB for the processor, 306 to 308 MiB per worker, 57 MiB for Postgres, 16 MiB for Redis; 1,361 MB used on the whole machine. Under a burst the workers peaked near 450 MiB each and the main at 410 MiB. Workers also burn the CPU, 80 to 95% of a core each in our burst, so the vCPU type matters most for them: VPS plans have shared vCPUs, VDS plans dedicated vCPUs.
| Situation | Layout | Plan | City |
|---|---|---|---|
| First queue-mode setup, a few thousand executions a day, light Code nodes | All on one server: main, processor, 2 workers, Postgres, Redis | VPS Mini: 4 vCPU (shared), 4 GB RAM, 80 GB NVMe. About 1.4 GB idle, 2 GB in bursts | Amsterdam, Dublin or Prishtina; Prishtina includes weekly backups |
| Production for a business, bursts of webhooks, steady CPU wanted | All on one server, 2 to 4 workers | VDS Small: 4 vCPU (dedicated), 8 GB RAM, 240 GB NVMe, 10 Gb/s | The city nearest your users and APIs |
| A dead server should cost capacity, not everything | A: main, processor, Postgres, Redis. B: 2 to 3 workers over WireGuard | A: VDS Small. B: VPS Mini for light workflows, a second VDS Small for heavy Code or AI nodes | Both servers in the same city |
| Heavy Code nodes, AI agents, big files, 5 or more workers | One big server, or A as above and a bigger B | VDS Medium: 8 vCPU (dedicated), 16 GB RAM, 480 GB NVMe | Same city for A and B |
| Enterprise licence with multi-main | Two mains behind a load balancer with sticky sessions, workers, a separate database server | Two VDS Small for the mains, VDS Medium for Postgres and Redis, workers on VPS Mini or VDS Small | Same city |
Every plan has NVMe, unmetered traffic and its own IPv4 and IPv6 address, and an upgrade from the client area is a short reboot, so starting on a VPS Mini and moving up when the worker CPU graph stays high is normal. Plans and install options are on the n8n VPS hosting page.
When you do need Enterprise multi-main
Multi-main means several mains, a leader that runs timers and pruning, and followers that take over when the leader misses its key TTL (10 seconds by default, checked every 3). It needs every main in queue mode on the same Postgres and Redis, the same n8n version everywhere, N8N_MULTI_MAIN_SETUP_ENABLED=true, sticky sessions and a licence (n8n docs). On the pricing page (read 28 October 2026) the self-hosted Business plan at 667 € a month billed annually does not list it; Enterprise is "Contact Sales". We did not test it. You need it when a 15-second gap in timers or the editor is a real problem, for example n8n in a paid customer-facing path; for an internal automation server the Community layout above is enough. The compliance side is in our n8n on-premise for companies post.
Frequently asked questions
How do I set up high availability for a self-hosted n8n instance?
Run n8n in queue mode: PostgreSQL and Redis, one main, one or more workers (n8n worker) and a webhook processor (n8n webhook), all with EXECUTIONS_MODE=queue and the same N8N_ENCRYPTION_KEY. Give every container restart: always and a health check, route /webhook/* to the processor, put workers on a second server over WireGuard, and monitor /healthz from outside. Two mains at once (multi-main) need an Enterprise licence.
Is n8n queue mode free in the Community edition?
Yes. The n8n documentation lists queue mode as included in the Community edition and names only multi-main and the Settings > Workers view as licensed. We ran one main, two workers and a webhook processor on n8n 2.45.5 without a licence key.
How much RAM does n8n queue mode need?
At idle in our test: 358 MiB for the main, about 310 MiB per worker, 254 MiB for a webhook processor, 57 MiB for Postgres and 16 MiB for Redis. Under a burst of 200 webhook executions the workers peaked near 450 MiB each and the main at 410 MiB. Plan 2 GB for main, two workers and the databases, and 0.5 GB per extra worker.
What happens to running executions when an n8n worker crashes?
They end as "crashed" and are not re-run. A worker we killed mid-burst had 11 executions in hand: 3 finished before the kill, 2 were marked crashed at once and 8 within five minutes. n8n 2.0 removed Bull's automatic retry of stalled jobs, so retries must come from an Error Workflow or the sender.
Can n8n workers run on a different server than the main instance?
Yes. A worker only needs Redis and PostgreSQL, so publish both on a private WireGuard address on the main server, give the worker the same N8N_ENCRYPTION_KEY, n8n version and passwords, and start it with n8n worker --concurrency=10. Never publish Redis or Postgres on a public IP; Docker's published ports bypass ufw.
What does n8n multi-main cost?
The n8n pricing page (read 28 October 2026) places multi-main under Enterprise with "Contact Sales" and no public price; the self-hosted Business plan at 667 € a month does not list it. The Community edition gives one main with unlimited workers at no cost.
Start with one main and two workers
Queue mode is the biggest reliability step a self-hosted n8n can take for free, and it fits on a VPS Mini to start. Tell us roughly how many executions a day and whether Code nodes are heavy, and we will say which plan and city fit, or install the setup above for you, quoted per job. Plans are on the n8n VPS hosting page; the fastest way to ask is Telegram.