--- title: "n8n en producción con PostgreSQL, Redis y modo queue" description: "Despliega n8n en Ubuntu 24.04 con Docker, PostgreSQL y Nginx, activa el modo queue con workers en Redis, protege webhooks y centraliza la gestión de errores." category: "Automatización de Workflows" language: es source: https://cubepath.com/es/docs/automatización-de-workflows/configuracion-avanzada-de-n8n-automatizacion updated: 2026-09-25 --- # n8n en producción con PostgreSQL, Redis y modo queue n8n es una plataforma de automatización de workflows de código abierto que conecta APIs, bases de datos y servicios mediante nodos visuales. La instalación por defecto usa SQLite y ejecuta todos los workflows en un solo proceso, algo suficiente para pruebas pero no para cargas reales. En este tutorial desplegarás n8n en Ubuntu 24.04 con Docker Compose, PostgreSQL como base de datos y Nginx con HTTPS, activarás el modo queue con workers que leen de Redis, protegerás los webhooks, configurarás un workflow de errores global y automatizarás las copias de seguridad. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 vCPU y 4 GB de RAM. - Un usuario no root con privilegios `sudo`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. - Un subdominio, por ejemplo `n8n.your_domain`, con un registro A apuntando a la IP del servidor. - Los puertos 80 y 443 abiertos en el firewall. ## Paso 1: Preparar los secretos n8n cifra todas las credenciales guardadas (tokens de API, contraseñas de bases de datos) con la clave `N8N_ENCRYPTION_KEY`. Si la clave cambia o se pierde, las credenciales existentes no se pueden descifrar, así que genérala una vez y guárdala en un lugar seguro. Crea el directorio del proyecto: ```bash sudo mkdir -p /opt/n8n cd /opt/n8n ``` Genera tres valores aleatorios, uno para la clave de cifrado y dos para las contraseñas de PostgreSQL y Redis: ```bash openssl rand -hex 32 openssl rand -hex 24 openssl rand -hex 24 ``` Crea el archivo `.env`, que Docker Compose lee automáticamente para sustituir las variables: ```bash sudo nano /opt/n8n/.env ``` ```ini N8N_DOMAIN=n8n.your_domain N8N_ENCRYPTION_KEY=your_64_char_encryption_key POSTGRES_PASSWORD=your_postgres_password REDIS_PASSWORD=your_redis_password GENERIC_TIMEZONE=Europe/Madrid ``` Sustituye los valores por los que acabas de generar y restringe los permisos del archivo: ```bash sudo chmod 600 /opt/n8n/.env ``` ## Paso 2: Crear el archivo de Docker Compose Este archivo define cuatro servicios: PostgreSQL, Redis, el proceso principal de n8n (interfaz, API, webhooks y disparadores) y los workers que ejecutan los workflows. Las variables comunes a n8n y a los workers se declaran una sola vez con un ancla YAML (`x-n8n-env`): ```bash sudo nano /opt/n8n/compose.yaml ``` ```yaml x-n8n-env: &n8n-env 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} EXECUTIONS_MODE: queue QUEUE_BULL_REDIS_HOST: redis QUEUE_BULL_REDIS_PORT: 6379 QUEUE_BULL_REDIS_PASSWORD: ${REDIS_PASSWORD} QUEUE_HEALTH_CHECK_ACTIVE: "true" GENERIC_TIMEZONE: ${GENERIC_TIMEZONE} TZ: ${GENERIC_TIMEZONE} EXECUTIONS_DATA_PRUNE: "true" EXECUTIONS_DATA_MAX_AGE: 336 EXECUTIONS_DATA_SAVE_ON_SUCCESS: none services: postgres: image: postgres:16 environment: POSTGRES_DB: n8n POSTGRES_USER: n8n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped redis: image: redis:7 command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"] volumes: - redis_data:/data healthcheck: test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep PONG"] interval: 10s timeout: 5s retries: 5 environment: REDIS_PASSWORD: ${REDIS_PASSWORD} restart: unless-stopped n8n: image: docker.n8n.io/n8nio/n8n:latest environment: <<: *n8n-env N8N_HOST: ${N8N_DOMAIN} N8N_PROTOCOL: https N8N_PORT: 5678 WEBHOOK_URL: https://${N8N_DOMAIN}/ N8N_PROXY_HOPS: 1 OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: "true" ports: - "127.0.0.1:5678:5678" volumes: - n8n_data:/home/node/.n8n depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped n8n-worker: image: docker.n8n.io/n8nio/n8n:latest command: worker --concurrency=10 environment: <<: *n8n-env volumes: - n8n_data:/home/node/.n8n depends_on: - n8n restart: unless-stopped volumes: postgres_data: redis_data: n8n_data: ``` Puntos clave de esta configuración: - `EXECUTIONS_MODE: queue` hace que el proceso principal no ejecute workflows: los encola en Redis y los workers los recogen. Así puedes añadir workers cuando crezca la carga. - `OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS` envía también a los workers las ejecuciones que lanzas desde el editor. - `EXECUTIONS_DATA_PRUNE` y `EXECUTIONS_DATA_MAX_AGE` (en horas, 336 son 14 días) evitan que la tabla de ejecuciones crezca sin límite. Con `EXECUTIONS_DATA_SAVE_ON_SUCCESS: none` solo se guardan las ejecuciones fallidas, que son las que necesitas revisar. - `N8N_PROXY_HOPS: 1` indica a n8n que hay un proxy delante, para que registre la IP real del cliente. - El puerto 5678 solo escucha en `127.0.0.1`; el acceso público pasa por Nginx. Arranca la pila: ```bash sudo docker compose up -d ``` Comprueba que los cuatro contenedores están en marcha y que PostgreSQL y Redis aparecen como `healthy`: ```bash sudo docker compose ps ``` ```output NAME IMAGE STATUS n8n-n8n-1 docker.n8n.io/n8nio/n8n:latest Up 30 seconds n8n-n8n-worker-1 docker.n8n.io/n8nio/n8n:latest Up 29 seconds n8n-postgres-1 postgres:16 Up 41 seconds (healthy) n8n-redis-1 redis:7 Up 41 seconds (healthy) ``` Revisa que el worker se ha conectado a la cola: ```bash sudo docker compose logs n8n-worker --tail 20 ``` La salida debe indicar que el worker está listo y escuchando trabajos, sin errores de conexión a Redis o PostgreSQL. Comprueba también el endpoint de salud del proceso principal: ```bash curl -s http://127.0.0.1:5678/healthz ``` ```output {"status":"ok"} ``` ## Paso 3: Publicar n8n con Nginx y HTTPS Instala Nginx y Certbot: ```bash sudo apt update sudo apt install nginx certbot python3-certbot-nginx ``` Crea el bloque de servidor. n8n usa WebSockets para actualizar el editor en tiempo real, por eso se pasan las cabeceras `Upgrade` y `Connection`. `proxy_buffering off` evita retrasos en las respuestas en streaming: ```bash sudo nano /etc/nginx/sites-available/n8n ``` ```nginx server { listen 80; listen [::]:80; server_name n8n.your_domain; client_max_body_size 50m; location / { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; 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 Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; proxy_read_timeout 3600s; } } ``` Activa el sitio, valida la configuración y solicita el certificado: ```bash sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx sudo ufw allow 'Nginx Full' sudo certbot --nginx -d n8n.your_domain ``` Abre `https://n8n.your_domain`. La primera vez n8n te pedirá crear la cuenta de propietario (owner) con tu email y una contraseña. Hazlo de inmediato: hasta entonces, cualquiera que llegue a la URL podría reclamar la instancia. Después, en **Settings** > **Personal**, activa la autenticación en dos pasos. ## Paso 4: Recibir webhooks autenticados Un nodo **Webhook** expone una URL que dispara el workflow. n8n genera dos URLs para cada webhook: - `https://n8n.your_domain/webhook-test/`: solo funciona mientras el editor está escuchando (**Listen for test event**). Sirve para desarrollar. - `https://n8n.your_domain/webhook/`: la URL de producción, que funciona cuando el workflow está activado (o publicado, según la versión de n8n). Cualquiera que conozca la URL de producción puede llamarla, así que conviene exigir un secreto. Crea un workflow nuevo, añade un nodo **Webhook** y configúralo así: 1. **HTTP Method**: `POST`. 2. **Path**: `pedidos`. 3. **Authentication**: **Header Auth**. Crea una credencial nueva con **Name** `X-Webhook-Secret` y como **Value** un secreto largo (por ejemplo, la salida de `openssl rand -hex 24`). 4. **Respond**: **Immediately**, para que el cliente reciba un `200` sin esperar a que termine el workflow. Añade detrás cualquier nodo de prueba (por ejemplo, **No Operation, do nothing**), guarda y activa el workflow. Envía una petición con el secreto: ```bash curl -i -X POST https://n8n.your_domain/webhook/pedidos \ -H "X-Webhook-Secret: your_webhook_secret" \ -H "Content-Type: application/json" \ -d '{"pedido_id": 1001, "total": 150}' ``` ```output HTTP/1.1 200 OK ... {"message":"Workflow was started"} ``` Repite la petición sin la cabecera: n8n debe responder `403 Forbidden`. En la pestaña **Executions** del workflow verás la ejecución con los datos recibidos. Como está en modo queue, la ha procesado el worker, no el proceso principal. ## Paso 5: Centralizar la gestión de errores Cuando un workflow activo falla, n8n puede lanzar automáticamente otro workflow que te avise. Crea un workflow llamado `Errores globales` y añade como disparador el nodo **Error Trigger**. Detrás, añade el nodo de notificación que uses (Slack, Telegram, email). En el texto del mensaje puedes usar expresiones con los datos del fallo: ```text Fallo en el workflow {{ $json.workflow.name }} Nodo: {{ $json.execution.lastNodeExecuted }} Error: {{ $json.execution.error.message }} Ejecución: {{ $json.execution.url }} ``` Guarda el workflow. Después, en cada workflow de producción, abre **Settings** (menú de los tres puntos) y en **Error workflow** selecciona `Errores globales`. Para comprobarlo, añade temporalmente a un workflow de prueba un nodo **Stop and Error** con un mensaje, actívalo y dispáralo: debes recibir la notificación con el nombre del workflow y el mensaje de error. Si prefieres tratar un fallo dentro del propio workflow en lugar de detenerlo, abre la configuración de un nodo (pestaña **Settings**) y cambia **On Error** a **Continue (using error output)**. El nodo tendrá una segunda salida por la que salen solo los elementos que fallaron, y ahí puedes conectar un reintento o un aviso. ## Paso 6: Reutilizar lógica con sub-workflows Si varios workflows repiten los mismos pasos (por ejemplo, enviar una notificación con un formato concreto), muévelos a un sub-workflow: 1. Crea un workflow nuevo que empiece por el disparador **When Executed by Another Workflow** (en versiones anteriores, **Execute Workflow Trigger**). Define los campos de entrada que espera, por ejemplo `destinatario` y `mensaje`. 2. Añade los nodos que hacen el trabajo y guarda. 3. En el workflow principal, añade el nodo **Execute Workflow**, elige el sub-workflow en **From list** y rellena los campos de entrada con expresiones, como `{{ $json.email }}`. Los datos que devuelva el último nodo del sub-workflow vuelven al workflow principal. Ejecuta el principal desde el editor y comprueba en **Executions** que aparecen ambas ejecuciones enlazadas. ## Paso 7: Escalar los workers Cada worker procesa hasta 10 ejecuciones simultáneas (`--concurrency=10`). Para aumentar la capacidad, añade más réplicas del servicio de workers: ```bash cd /opt/n8n sudo docker compose up -d --scale n8n-worker=3 ``` ```bash sudo docker compose ps n8n-worker ``` ```output NAME IMAGE STATUS n8n-n8n-worker-1 docker.n8n.io/n8nio/n8n:latest Up 12 minutes n8n-n8n-worker-2 docker.n8n.io/n8nio/n8n:latest Up 8 seconds n8n-n8n-worker-3 docker.n8n.io/n8nio/n8n:latest Up 8 seconds ``` Vigila el consumo con `sudo docker stats` antes de añadir más: cada worker necesita memoria propia, y los workflows que manejan archivos grandes pueden consumir cientos de MB por ejecución. Para que el número de réplicas se mantenga tras un `docker compose up -d` posterior, añade `deploy: { replicas: 3 }` al servicio `n8n-worker` en `compose.yaml`. ## Paso 8: Hacer copias de seguridad Necesitas dos cosas para recuperar una instancia: la base de datos de PostgreSQL y la clave `N8N_ENCRYPTION_KEY` del archivo `.env`. Crea un script que vuelque la base de datos a diario: ```bash sudo nano /usr/local/bin/n8n-backup ``` ```bash #!/usr/bin/env bash set -euo pipefail BACKUP_DIR=/var/backups/n8n mkdir -p "$BACKUP_DIR" cd /opt/n8n docker compose exec -T postgres pg_dump -U n8n -d n8n --format=custom > "$BACKUP_DIR/n8n-$(date +%F).dump" find "$BACKUP_DIR" -name 'n8n-*.dump' -mtime +14 -delete ``` Hazlo ejecutable, pruébalo y comprueba que se ha creado el volcado: ```bash sudo chmod 750 /usr/local/bin/n8n-backup sudo /usr/local/bin/n8n-backup ls -lh /var/backups/n8n ``` Prográmalo con cron para que se ejecute cada noche a las 02:30: ```bash echo '30 2 * * * root /usr/local/bin/n8n-backup' | sudo tee /etc/cron.d/n8n-backup ``` Copia `/var/backups/n8n` y el archivo `/opt/n8n/.env` a un almacenamiento fuera del servidor. Además, puedes exportar los workflows como JSON, útil para llevarlos a control de versiones: ```bash sudo docker compose exec n8n n8n export:workflow --all --separate --output=/home/node/.n8n/export/ ``` ## Paso 9: Actualizar n8n Antes de actualizar, lee las notas de la versión en el changelog de n8n, sobre todo si cambia la versión principal. Haz una copia con el script anterior y actualiza las imágenes: ```bash cd /opt/n8n sudo /usr/local/bin/n8n-backup sudo docker compose pull sudo docker compose up -d ``` n8n aplica automáticamente las migraciones de la base de datos al arrancar. Revisa los logs con `sudo docker compose logs n8n --tail 50` para confirmar que han terminado sin errores. En producción es preferible fijar una versión concreta de la imagen (la etiqueta con el número de versión, como `docker.n8n.io/n8nio/n8n:`) en lugar de `latest`, para controlar cuándo se actualiza. ## Solución de problemas **Las URLs de webhook que muestra el editor usan `http://localhost:5678`.** Falta `WEBHOOK_URL` o está mal escrita. Corrígela en `compose.yaml` y recrea el contenedor con `sudo docker compose up -d`. **El webhook de producción devuelve 404.** El workflow no está activo, o estás llamando a `/webhook-test/` en lugar de `/webhook/`. **Las ejecuciones se quedan en espera y nunca terminan.** Ningún worker está leyendo la cola. Comprueba con `sudo docker compose logs n8n-worker` que los workers arrancan y que usan la misma contraseña de Redis y la misma `N8N_ENCRYPTION_KEY` que el proceso principal. **`Credentials could not be decrypted`.** La `N8N_ENCRYPTION_KEY` actual no es la que se usó para guardar las credenciales. Restaura el valor original desde tu copia del archivo `.env`. **El editor se desconecta o no muestra el progreso de las ejecuciones.** Nginx no está pasando las cabeceras de WebSocket. Revisa que el bloque de servidor incluye `Upgrade` y `Connection` y recarga Nginx. ## Conclusión Tienes n8n funcionando en modo queue con PostgreSQL, Redis y workers escalables, publicado con HTTPS, con webhooks autenticados, un workflow de errores global y copias de seguridad diarias. Como siguientes pasos, puedes crear una clave en **Settings** > **n8n API** para gestionar workflows desde scripts, mover los workflows exportados a un repositorio Git y añadir procesos dedicados de webhooks si recibes un volumen alto de peticiones entrantes.