Escala n8n: la guía definitiva del modo cola y Docker
Aprende a escalar eficazmente los flujos de trabajo de n8n usando el modo cola y Docker para optimizar el rendimiento y la fiabilidad en procesos de automatización.
Escrito por
Idir Ouhab Meskine

¿Qué problema resolvemos?
Por defecto, n8n funciona como un único proceso que hace todo: muestra la interfaz, recibe webhooks, ejecuta workflows, gestiona triggers... Cuando tienes muchos workflows o tráfico alto, ese proceso único se convierte en un cuello de botella.
La solución es separar responsabilidades:
| Componente | ¿Qué hace? |
|---|---|
| Main | Muestra el editor, gestiona la API y los triggers (cron, polling) |
| Worker | Ejecuta los workflows (el trabajo pesado) |
| Webhook Processor | Recibe las peticiones HTTP de webhooks |
| Task Runner | Ejecuta el código de los nodos Code (JS/Python) de forma aislada y segura |
| Redis | Cola de mensajes que conecta todo |
| PostgreSQL | Base de datos compartida |
Piensa en ello como un restaurante: el Main es el maître que recibe pedidos, Redis es la barra donde se dejan las comandas, el Worker es el cocinero, el Webhook Processor es la puerta de entrada para pedidos online y los Task Runners son los ayudantes especializados que preparan ingredientes específicos.
Arquitectura visual
┌─────────────────┐
│ Reverse Proxy │
│ (Nginx/Traefik) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ │ ▼
┌──────────┐ │ ┌──────────────┐
│ Main │ │ │ Webhook │
│ Editor │ │ │ Processor │
│ API │ │ │ /webhook/* │
│ Triggers│ │ └──────┬───────┘
└────┬─────┘ │ │
│ ┌──────────┘ │
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐
│ Redis (cola) │
└──────────────────┬───────────────────┘
│
▼
┌──────────┐
│ Worker │
└────┬─────┘
│
▼
┌──────────┐
│ Runner │
│ (sidecar)│
└──────────┘
│
▼
┌─────────────────┐
│ PostgreSQL │
└─────────────────┘Requisitos previos
- Un servidor Linux (Ubuntu 22.04+ recomendado)
- Docker y Docker Compose instalados
- Un dominio apuntando a tu servidor (para HTTPS)
- Mínimo 4 GB RAM / 2 vCPU
Paso 1: Crear la estructura del proyecto
mkdir n8n-scaling && cd n8n-scaling
touch docker-compose.yml .envPaso 2: Configurar las variables de entorno
Crea tu archivo .env. Cambia las contraseñas por las tuyas:
# General
N8N_VERSION=1.71.3
GENERIC_TIMEZONE=Europe/Berlin
N8N_ENCRYPTION_KEY=tu-clave-secreta-muy-larga-aqui
# PostgreSQL
POSTGRES_USER=n8n
POSTGRES_PASSWORD=cambia-esta-password
POSTGRES_DB=n8n
# Redis
REDIS_PASSWORD=cambia-esta-password-redis
# n8n
N8N_HOST=n8n.tudominio.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.tudominio.com
# Queue Mode
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
DB_POSTGRESDB_USER=${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
# Task Runner
N8N_RUNNERS_MODE=external
N8N_RUNNERS_AUTH_TOKEN=tu-token-secreto-para-runners
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true️ Importante: El N8N_ENCRYPTION_KEY debe ser idéntico en todos los procesos (main, worker, webhook). Si no coincide, n8n no podrá leer las credenciales almacenadas. Tip: Para generar claves seguras puedes usar: openssl rand -hex 32Paso 3: Crear el docker-compose.yml
volumes:
postgres_data:
redis_data:
n8n_data:
x-n8n-common: &n8n-common
image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
environment: &n8n-env
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
TZ: ${GENERIC_TIMEZONE}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
DB_TYPE: ${DB_TYPE}
DB_POSTGRESDB_HOST: ${DB_POSTGRESDB_HOST}
DB_POSTGRESDB_PORT: ${DB_POSTGRESDB_PORT}
DB_POSTGRESDB_DATABASE: ${DB_POSTGRESDB_DATABASE}
DB_POSTGRESDB_USER: ${DB_POSTGRESDB_USER}
DB_POSTGRESDB_PASSWORD: ${DB_POSTGRESDB_PASSWORD}
EXECUTIONS_MODE: ${EXECUTIONS_MODE}
QUEUE_BULL_REDIS_HOST: ${QUEUE_BULL_REDIS_HOST}
QUEUE_BULL_REDIS_PORT: ${QUEUE_BULL_REDIS_PORT}
QUEUE_BULL_REDIS_PASSWORD: ${QUEUE_BULL_REDIS_PASSWORD}
N8N_RUNNERS_MODE: ${N8N_RUNNERS_MODE}
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- n8n-net
services:
# ══════════════════════════════════════════════════════
# PostgreSQL
# ══════════════════════════════════════════════════════
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- n8n-net
# ══════════════════════════════════════════════════════
# Redis
# ══════════════════════════════════════════════════════
redis:
image: redis:7-alpine
restart: unless-stopped
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--maxmemory 256mb
--maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- n8n-net
# ══════════════════════════════════════════════════════
# n8n Main
# ══════════════════════════════════════════════════════
n8n-main:
<<: *n8n-common
hostname: n8n-main
environment:
<<: *n8n-env
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
WEBHOOK_URL: ${WEBHOOK_URL}
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: ${OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS}
N8N_RUNNERS_SERVER_URI: http://n8n-runner-main:5679
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
# Runner sidecar para Main
n8n-runner-main:
image: docker.n8n.io/n8nio/runners:${N8N_VERSION}
restart: unless-stopped
hostname: n8n-runner-main
environment:
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_N8N_URI: http://n8n-main:5678
N8N_RUNNERS_MAX_CONCURRENCY: 5
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
depends_on:
- n8n-main
networks:
- n8n-net
# ══════════════════════════════════════════════════════
# Webhook Processor
# ══════════════════════════════════════════════════════
n8n-webhook:
<<: *n8n-common
hostname: n8n-webhook
command: webhook
environment:
<<: *n8n-env
WEBHOOK_URL: ${WEBHOOK_URL}
N8N_RUNNERS_SERVER_URI: http://n8n-runner-webhook:5679
ports:
- "5680:5678"
# Runner sidecar para Webhook
n8n-runner-webhook:
image: docker.n8n.io/n8nio/runners:${N8N_VERSION}
restart: unless-stopped
hostname: n8n-runner-webhook
environment:
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_N8N_URI: http://n8n-webhook:5678
N8N_RUNNERS_MAX_CONCURRENCY: 5
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
depends_on:
- n8n-webhook
networks:
- n8n-net
# ══════════════════════════════════════════════════════
# Worker
# ══════════════════════════════════════════════════════
n8n-worker:
<<: *n8n-common
hostname: n8n-worker
command: worker
environment:
<<: *n8n-env
QUEUE_WORKER_CONCURRENCY: 10
N8N_RUNNERS_SERVER_URI: http://n8n-runner-worker:5679
# Runner sidecar para Worker
n8n-runner-worker:
image: docker.n8n.io/n8nio/runners:${N8N_VERSION}
restart: unless-stopped
hostname: n8n-runner-worker
environment:
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_N8N_URI: http://n8n-worker:5678
N8N_RUNNERS_MAX_CONCURRENCY: 5
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
depends_on:
- n8n-worker
networks:
- n8n-net
networks:
n8n-net:
driver: bridgePaso 4: Entender cada componente
️PostgreSQL
La base de datos donde n8n guarda workflows, credenciales y ejecuciones. Es obligatorio para queue mode — SQLite no es compatible.
Redis
El mensajero que conecta todo. Cuando el main recibe un trigger o el webhook processor recibe una petición, no ejecutan el workflow directamente. En su lugar, ponen un mensaje en Redis diciendo "hay trabajo pendiente" y el worker lo recoge.
️Main
Sirve el editor visual, la API REST y gestiona los triggers (cron, polling, conexiones persistentes como IMAP o RabbitMQ). No ejecuta workflows — eso lo delega al worker a través de Redis.
Webhook Processor
Un proceso separado dedicado exclusivamente a recibir webhooks (/webhook/*). Se arranca con el comando webhook. Esto evita que un pico de tráfico en webhooks afecte al editor.
Worker
El que hace el trabajo pesado. Escucha la cola de Redis, toma ejecuciones pendientes y las procesa. QUEUE_WORKER_CONCURRENCY=10 significa que puede ejecutar hasta 10 workflows en paralelo.
Task Runners (Modo Externo)
A partir de n8n 2.0, los task runners vienen activados por defecto. Ejecutan el código de los nodos Code (JavaScript y Python) en un entorno aislado.
En modo externo (N8N_RUNNERS_MODE=external), cada proceso de n8n necesita su propio runner como sidecar — un contenedor compañero que corre a su lado. Por eso en el docker-compose ves tres runners: uno para el main, uno para el webhook y uno para el worker.
¿Por qué importa?
- Seguridad: Un código con errores en un Code node no puede tumbar n8n.
- Aislamiento: Los runners no tienen acceso a las variables de entorno ni al filesystem de n8n.
- Estabilidad: Un script que consume mucha memoria se mata sin afectar a nada más.
Importante: La versión de la imagenn8nio/runnersdebe coincidir exactamente con la versión den8nio/n8n.
Paso 5: Arrancar todo
# Levantar la infraestructura
docker compose up -d
# Verificar que todo está corriendo
docker compose ps
# Ver los logs en tiempo real
docker compose logs -fDeberías ver algo como:
n8n-main | n8n ready on 0.0.0.0, port 5678
n8n-webhook | Webhook listener ready on 0.0.0.0, port 5678
n8n-worker | Worker started successfullyPaso 6: Configurar el Reverse Proxy (Nginx)
Necesitas un reverse proxy para enrutar el tráfico correctamente:
server {
listen 443 ssl;
server_name n8n.tudominio.com;
ssl_certificate /etc/letsencrypt/live/n8n.tudominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.tudominio.com/privkey.pem;
# Webhooks de producción → Webhook Processor
location /webhook/ {
proxy_pass http://127.0.0.1:5680;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
# Webhooks de test → Main (botón "Test workflow")
location /webhook-test/ {
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Todo lo demás (editor, API) → Main
location / {
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_buffering off;
}
}La regla clave: /webhook/* va al puerto 5680 (webhook processor) y todo lo demás al 5678 (main).
Paso 7: Verificar que todo funciona
- [ ]
docker compose ps— todos los contenedores en "running" - [ ] Accedes al editor en
https://n8n.tudominio.com - [ ] Crear y guardar un workflow funciona
- [ ] Un webhook trigger recibe peticiones externas
- [ ] El botón "Test workflow" funciona desde el editor
- [ ] Un Code node ejecuta código sin errores (runner funcionando)
Errores comunes
"Could not find encryption key" — El N8N_ENCRYPTION_KEY no es el mismo en todos los servicios. Revisa tu .env.
Los webhooks no llegan — Verifica que tu reverse proxy enruta /webhook/* al puerto 5680, no al 5678.
El worker no procesa nada — Revisa que puede conectarse a Redis y PostgreSQL: docker compose logs n8n-worker.
Task runner "unhealthy" — La versión de n8nio/runners no coincide con n8nio/n8n. Deben ser idénticas.
Resumen de variables clave
| Variable | Valor | Descripción |
|---|---|---|
EXECUTIONS_MODE | queue | Activa queue mode |
N8N_ENCRYPTION_KEY | (tu clave) | Idéntica en TODOS los procesos |
N8N_RUNNERS_MODE | external | Task runners en contenedores separados |
N8N_RUNNERS_AUTH_TOKEN | (tu token) | Autenticación entre n8n y runners |
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS | true | Ejecuciones manuales van al worker |
QUEUE_WORKER_CONCURRENCY | 10 | Ejecuciones paralelas del worker |
WEBHOOK_URL | https://tu.dominio | URL pública para webhooks |
¿Cuándo necesitas esto?
- < 1.000 ejecuciones/día: n8n normal con PostgreSQL es suficiente.
- 1.000 - 10.000 ejecuciones/día: Queue mode con 1 worker.
- > 10.000 ejecuciones/día: Añade más workers y webhook processors.
- Ejecutas código en Code nodes: Task runners en modo externo (obligatorio en n8n 2.0+).