Proteger n8n con reverse proxy Nginx, TLS y cabeceras de seguridad

15 min de lectura·Matthieu·webhookssecuritylets-encryptreverse-proxynginxn8n|

Coloca tu instancia de n8n autoalojada detrás de Nginx con TLS de Let's Encrypt, cabeceras de seguridad, limitación de tasa, reglas de firewall y protección de webhooks. Cada paso incluye un comando de verificación.

Este tutorial coloca tu instancia de n8n autoalojada detrás de Nginx con TLS, cabeceras de seguridad, limitación de tasa y reglas de firewall. Pasarás de un n8n expuesto en el puerto 5678 a una configuración lista para producción donde solo el tráfico HTTPS llega al editor y a los webhooks.

Requisitos previos:

  • n8n en ejecución con Docker Compose en un VPS ()
  • Un nombre de dominio (p. ej. n8n.example.com) con un registro A apuntando a la IP de tu servidor
  • Acceso SSH al servidor como usuario no root con sudo
  • Puertos 80 y 443 abiertos en el firewall de tu proveedor de hosting (Virtua Cloud los abre por defecto)

Esta guía asume Ubuntu 24.04 LTS. Los comandos funcionan en Debian 12 con ajustes menores. Se usa n8n versión 2.x en todo el tutorial.

¿Cómo configuro Nginx como reverse proxy para n8n con soporte de WebSocket?

Nginx se sitúa entre internet y n8n, reenviando las peticiones a localhost:5678. El editor de n8n depende de conexiones WebSocket para las actualizaciones en tiempo real. Sin un proxy de WebSocket correctamente configurado, la interfaz del editor se congela y muestra "Connection lost." Necesitas proxy_pass hacia localhost:5678, cabeceras de actualización HTTP/1.1 para WebSocket y la variable de entorno N8N_PROXY_HOPS=1 para que n8n lea la IP real del cliente desde X-Forwarded-For.

Instalar Nginx

sudo apt update && sudo apt install -y nginx

Verifica que Nginx está en ejecución:

sudo systemctl status nginx

Deberías ver active (running) en verde. Si no es así, inícialo y habilítalo:

sudo systemctl enable --now nginx

El flag enable --now hace dos cosas: enable hace que Nginx arranque automáticamente tras un reinicio, y --now lo inicia de inmediato.

Crear el bloque de servidor Nginx

Crea un nuevo archivo de configuración para tu dominio de n8n:

sudo nano /etc/nginx/sites-available/n8n.example.com

Pega esta configuración. Sustituye n8n.example.com por tu dominio real:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name n8n.example.com;

    # Will be replaced by Certbot later
    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;
        proxy_cache off;
        chunked_transfer_encoding off;
    }
}

El bloque map en la parte superior gestiona las actualizaciones de WebSocket. Cuando un navegador envía una cabecera Upgrade: websocket, Nginx la deja pasar. Para peticiones HTTP normales, envía close. Esto mantiene el editor de n8n con buena respuesta.

proxy_buffering off y proxy_cache off evitan que Nginx almacene en búfer los server-sent events de n8n, lo que provocaría retardos en el editor.

Habilita el sitio y comprueba la configuración:

sudo ln -s /etc/nginx/sites-available/n8n.example.com /etc/nginx/sites-enabled/
sudo nginx -t

Deberías ver:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Si la prueba pasa, recarga:

sudo systemctl reload nginx

Verifica que n8n es accesible a través de Nginx:

curl -s -o /dev/null -w "%{http_code}" http://n8n.example.com

Una respuesta 200 significa que Nginx está reenviando el tráfico a n8n. Un 502 significa que n8n no está en ejecución en el puerto 5678. Compruébalo con docker ps.

Actualizar las variables de entorno de n8n

Abre tu archivo .env de n8n (en el mismo directorio que tu docker-compose.yml):

nano ~/n8n-docker/.env

Añade o actualiza estas variables:

N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
N8N_PROXY_HOPS=1
WEBHOOK_URL=https://n8n.example.com/
N8N_EDITOR_BASE_URL=https://n8n.example.com/
N8N_SECURE_COOKIE=true

Qué hace cada variable:

Variable Valor Propósito
N8N_HOST Tu dominio Indica a n8n qué nombre de host usar en las URL de webhooks
N8N_PROTOCOL https n8n genera URL de webhook HTTPS en lugar de HTTP
N8N_PROXY_HOPS 1 n8n confía en una capa de X-Forwarded-For para obtener la IP real del cliente
WEBHOOK_URL URL completa Sobrescribe la URL base de webhook generada automáticamente
N8N_EDITOR_BASE_URL URL completa URL usada en las notificaciones por correo y redirecciones SAML
N8N_SECURE_COOKIE true Las cookies solo se envían por HTTPS. Previene el secuestro de sesión en HTTP plano

Reinicia n8n para aplicar los cambios:

cd ~/n8n-docker && docker compose down && docker compose up -d

Verifica que n8n cargó las nuevas variables de entorno:

docker compose logs --tail=20 n8n | grep -i "editor\|webhook\|proxy"

Deberías ver líneas de log que hacen referencia a tu dominio y al protocolo HTTPS.

¿Cómo añado TLS de Let's Encrypt a mi reverse proxy de n8n?

Certbot automatiza la emisión de certificados de Let's Encrypt y configura Nginx para TLS automáticamente. Tras ejecutar Certbot, todo el tráfico HTTP se redirige a HTTPS, y tanto el editor de n8n como los webhooks están cifrados en tránsito.

Instalar Certbot

sudo apt install -y certbot python3-certbot-nginx

Obtener el certificado

sudo certbot --nginx -d n8n.example.com

Certbot hará lo siguiente:

  1. Verificar que eres propietario del dominio mediante un desafío HTTP-01
  2. Obtener un certificado de Let's Encrypt
  3. Modificar tu configuración de Nginx para añadir los ajustes de TLS y una redirección HTTP-a-HTTPS

Cuando te pida una dirección de correo electrónico, introduce una real. Recibirás avisos de fallo de renovación en esa dirección.

Verificar que TLS funciona

curl -I https://n8n.example.com

Busca HTTP/2 200 en la respuesta. Si ves un error de certificado, espera unos minutos para la propagación del DNS.

Comprueba la cadena de certificados desde tu máquina local (no el servidor):

openssl s_client -connect n8n.example.com:443 -servername n8n.example.com </dev/null 2>/dev/null | head -20

Busca Verify return code: 0 (ok).

Verificar la renovación automática

Certbot instala un temporizador de systemd que renueva los certificados antes de que expiren. Confirma que está activo:

sudo systemctl status certbot.timer

Deberías ver active (waiting). Prueba el proceso de renovación sin renovar realmente:

sudo certbot renew --dry-run

Un dry run correcto significa que tus certificados se renovarán automáticamente cada 60-90 días.

¿Qué cabeceras de seguridad necesita n8n en producción?

Las cabeceras de seguridad indican a los navegadores cómo manejar el contenido de tu sitio. Sin ellas, tu editor de n8n es vulnerable a clickjacking, ataques de sniffing de tipo MIME y cross-site scripting. Añade seis cabeceras a tu configuración de Nginx para cerrar estas brechas.

Abre la configuración de Nginx que Certbot modificó:

sudo nano /etc/nginx/sites-available/n8n.example.com

Dentro del bloque server que escucha en el puerto 443 (el que creó Certbot), añade estas cabeceras encima del bloque location /:

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://n8n.example.com; frame-ancestors 'none'" always;

Sustituye n8n.example.com en la directiva CSP connect-src por tu dominio real. La entrada wss:// permite las conexiones WebSocket al editor.

Qué hace cada cabecera:

Cabecera Valor Qué previene
Strict-Transport-Security 1 año, subdominios El navegador siempre usa HTTPS. Detiene ataques de SSL stripping
X-Frame-Options DENY Nadie puede incrustar tu editor de n8n en un iframe. Detiene clickjacking
X-Content-Type-Options nosniff El navegador confía en el tipo MIME declarado. Detiene ataques de confusión de MIME
Referrer-Policy strict-origin-when-cross-origin Limita la información de referencia filtrada a sitios externos
Permissions-Policy Deniega cámara, micro, geo El navegador bloquea el acceso a APIs de hardware que n8n no necesita
Content-Security-Policy Ver arriba Controla qué scripts, estilos y conexiones permite el navegador

La cabecera CSP es la más compleja. El editor de n8n usa scripts inline y eval() para el constructor de flujos de trabajo, por lo que 'unsafe-inline' y 'unsafe-eval' son necesarios para script-src. Es un compromiso conocido. Las demás directivas son restrictivas: solo recursos del mismo origen y conexiones WebSocket a tu dominio.

Prueba y recarga:

sudo nginx -t && sudo systemctl reload nginx

Verifica que las cabeceras están presentes:

curl -sI https://n8n.example.com | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"

Deberías ver las seis cabeceras en la salida. Si falta alguna, revisa si hay erratas en el archivo de configuración.

¿Cómo configuro el firewall para n8n en un VPS?

UFW bloquea todo el tráfico excepto lo que permitas expresamente. Para n8n detrás de Nginx, solo tres puertos deben estar abiertos: 22 (SSH), 80 (HTTP, para renovación de Certbot y redirecciones) y 443 (HTTPS). El puerto 5678, donde n8n escucha directamente, debe estar bloqueado desde el exterior. Muchas guías omiten este paso, dejando n8n accesible sin TLS.

Para una visión más detallada de UFW, consulta .

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP - Certbot renewal'
sudo ufw allow 443/tcp comment 'HTTPS - n8n via Nginx'
sudo ufw enable

Cuando se te pida confirmación, escribe y. Verifica las reglas:

sudo ufw status verbose

Deberías ver tres reglas ALLOW para los puertos 22, 80 y 443. Nada más. El puerto 5678 no aparece, lo que significa que está bloqueado.

Confirma que n8n no es accesible directamente. Desde tu máquina local:

curl -s --connect-timeout 3 http://n8n.example.com:5678 || echo "Connection refused - correct!"

Si recibes "Connection refused" o un timeout, el firewall funciona. Si recibes una respuesta de n8n, es probable que Docker esté saltándose UFW. Docker modifica iptables directamente y puede anular las reglas de UFW.

Vincular n8n a localhost y evitar que Docker evite UFW

La solución más sencilla es vincular n8n solo a localhost. En tu docker-compose.yml, cambia el mapeo de puertos:

    ports:
      - "127.0.0.1:5678:5678"

Esto asegura que n8n solo acepta conexiones desde la misma máquina. Nginx en el mismo servidor puede alcanzarlo, pero el tráfico externo no.

Reinicia n8n:

cd ~/n8n-docker && docker compose down && docker compose up -d

Si eso solo no lo soluciona (prueba de nuevo con curl desde tu máquina local), también puedes desactivar la gestión de iptables de Docker. Edita /etc/docker/daemon.json:

sudo nano /etc/docker/daemon.json

Añade:

{
  "iptables": false
}

Luego reinicia Docker:

sudo systemctl restart docker
cd ~/n8n-docker && docker compose up -d

Aviso: Establecer "iptables": false impide que Docker cree reglas de NAT y reenvío. Esto puede romper la comunicación entre contenedores y el acceso a internet saliente desde los contenedores. Si tus flujos de trabajo de n8n hacen peticiones HTTP a APIs externas (la mayoría lo hacen), prueba la conectividad saliente tras este cambio. El binding a localhost suele ser suficiente por sí solo.

Verifica de nuevo desde tu máquina local que el puerto 5678 es inaccesible.

¿Cómo limito la tasa de peticiones en los endpoints de webhook de n8n con Nginx?

La limitación de tasa protege tus endpoints de webhook contra abusos e intentos de denegación de servicio. Defines una tasa de peticiones por dirección IP. Las integraciones legítimas (GitHub, Stripe) envían webhooks a un ritmo predecible. Un atacante que bombardee tu URL de webhook recibe una respuesta 429 Too Many Requests en lugar de disparar flujos de trabajo.

Para más información sobre estrategias de limitación de tasa, consulta .

Añade una directiva limit_req_zone en la parte superior de tu archivo de configuración de Nginx, fuera de cualquier bloque server, junto a la directiva map existente:

limit_req_zone $binary_remote_addr zone=webhooks:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=editor:10m rate=30r/s;

Esto crea dos zonas:

  • webhooks: 10 peticiones por segundo por IP. Gestiona callbacks de servicios externos.
  • editor: 30 peticiones por segundo por IP. El editor realiza muchas llamadas API pequeñas, por lo que necesita un límite más alto.

Dentro del bloque server que escucha en 443, añade un bloque location separado para las rutas de webhook antes del location / principal:

    # Rate limit webhook endpoints
    location /webhook/ {
        limit_req zone=webhooks burst=20 nodelay;
        limit_req_status 429;

        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;

        # Generous timeout for long-running webhook workflows
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    location /webhook-test/ {
        limit_req zone=webhooks burst=5 nodelay;
        limit_req_status 429;

        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;
    }

El parámetro burst=20 permite picos cortos de hasta 20 peticiones antes de que se active la limitación. Esto cubre casos como GitHub enviando varios eventos de webhook desde un solo push. nodelay procesa las peticiones del pico inmediatamente en lugar de encolarlas.

En el bloque location / principal, añade la limitación de tasa del editor:

    location / {
        limit_req zone=editor burst=50 nodelay;
        limit_req_status 429;

        # ... existing proxy settings ...
    }

Prueba y recarga:

sudo nginx -t && sudo systemctl reload nginx

Verifica que la limitación funciona enviando una ráfaga de peticiones a un webhook de prueba:

for i in $(seq 1 30); do
    curl -s -o /dev/null -w "%{http_code} " https://n8n.example.com/webhook/test-rate-limit
done
echo

Deberías ver una mezcla de respuestas 404 (la ruta del webhook no existe, lo cual es normal) y respuestas 429 cuando se activa la limitación.

¿Cómo restrinjo el editor de n8n a direcciones IP específicas?

Por defecto, cualquiera que conozca la URL de tu n8n puede acceder a la página de inicio de sesión. La lista blanca de IP a nivel de Nginx añade una capa antes de la autenticación propia de n8n. Solo las peticiones desde tu dirección IP (o tu VPN) llegan al editor. Los endpoints de webhook permanecen abiertos a internet para que los servicios externos puedan usarlos.

Añade un nuevo bloque location para las rutas del editor. Colócalo después de las localizaciones de webhook y antes del location / principal:

    # Restrict editor/API access to specific IPs
    location /rest/ {
        allow 203.0.113.50;   # Your office/home IP
        allow 10.0.0.0/8;     # Your VPN range
        deny all;

        limit_req zone=editor burst=50 nodelay;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;
    }

Sustituye 203.0.113.50 por tu IP real. Encuéntrala con:

curl -4 ifconfig.me

La ruta /rest/ gestiona las llamadas API del editor de n8n. El location / principal sigue sirviendo el frontend del editor y los webhooks. Para un bloqueo más estricto, también puedes restringir la ruta raíz y añadir localizaciones abiertas separadas solo para /webhook/ y /webhook-test/.

Prueba y recarga:

sudo nginx -t && sudo systemctl reload nginx

Verifica desde tu IP permitida:

curl -s -o /dev/null -w "%{http_code}" https://n8n.example.com/rest/settings

Un 200 significa que estás permitido. Desde una IP diferente (usa un teléfono móvil sin estar en tu Wi-Fi), accede a https://n8n.example.com/rest/settings en un navegador. Deberías recibir un 403 Forbidden.

¿Cómo protejo los webhooks de n8n contra accesos no autorizados?

Las URL de webhook son públicas por defecto. Cualquiera que descubra o adivine la URL puede disparar tus flujos de trabajo. Dos estrategias protegen contra esto: mantener las rutas de webhook impredecibles y validar firmas HMAC dentro de tus flujos de trabajo.

Usar webhooks de producción con IDs únicos

n8n genera dos rutas de webhook para cada nodo Webhook:

  • URL de prueba: /webhook-test/<id> (activa solo mientras el editor está abierto)
  • URL de producción: /webhook/<id> (activa cuando el flujo de trabajo está activado)

El <id> es un UUID por defecto. No lo cambies por algo predecible como /webhook/github o /webhook/stripe. El UUID aleatorio es tu primera capa de defensa.

Validar firmas HMAC en los flujos de trabajo

Servicios como GitHub y Stripe firman sus cargas de webhook con un secreto compartido. Tu flujo de trabajo de n8n debería verificar esta firma antes de procesar los datos.

Para un webhook de GitHub, añade un nodo IF después del nodo Webhook con esta condición:

  1. En los ajustes de tu repositorio de GitHub, establece un secreto para el webhook (genera uno con openssl rand -base64 32)
  2. En tu flujo de trabajo de n8n, añade un nodo Code después del nodo Webhook:
const crypto = require('crypto');
const secret = $env.GITHUB_WEBHOOK_SECRET;
const signature = $input.first().headers['x-hub-signature-256'];
const body = JSON.stringify($input.first().json);
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');

if (signature !== expected) {
  throw new Error('Invalid webhook signature');
}

return $input.all();
  1. Almacena el secreto en el entorno de n8n, no codificado directamente en el flujo de trabajo. Añádelo a tu archivo .env:
GITHUB_WEBHOOK_SECRET=your-generated-secret-here

Y exponlo a n8n en docker-compose.yml:

    environment:
      - GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}

Para webhooks de Stripe, el patrón es similar pero usa una cabecera diferente (stripe-signature) y una comparación segura en tiempo. Consulta la documentación de firmas de webhook de Stripe para el esquema de firma actual.

Ocultar la información de versión de n8n

Por defecto, n8n incluye información de versión en las respuestas de la API. Desactiva la divulgación de versión para dificultar el reconocimiento:

En tu archivo .env:

N8N_VERSION_NOTIFICATIONS_ENABLED=false

En tu configuración de Nginx, añade dentro del bloque server:

    server_tokens off;

La divulgación de versión ayuda a los atacantes a atacar vulnerabilidades conocidas en versiones específicas de n8n. Ocultarla les obliga a sondear a ciegas.

¿Cómo ajusto los timeouts y límites de subida para n8n?

Algunos flujos de trabajo de n8n se ejecutan durante minutos, procesando grandes conjuntos de datos o esperando APIs externas. El timeout predeterminado de 60 segundos de Nginx cortará estas peticiones. Los flujos de subida de archivos fallan si la carga supera el límite predeterminado de 1 MB de Nginx.

En el bloque location / principal de tu servidor HTTPS, añade o actualiza:

    # Timeout tuning for long-running workflows
    proxy_connect_timeout 60s;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;

    # Allow file uploads up to 50 MB
    client_max_body_size 50m;

proxy_read_timeout 300s da a los flujos de trabajo hasta 5 minutos para responder. Ajústalo según el tiempo de ejecución de tu flujo de trabajo más largo. El bloque location de webhooks ya tiene sus propios ajustes de timeout de la sección de rate limiting.

client_max_body_size 50m permite subidas de hasta 50 MB a través de los endpoints de webhook y del editor. Los flujos de trabajo de n8n que procesan importaciones CSV, subidas de imágenes o conversiones de documentos lo necesitan.

Prueba y recarga:

sudo nginx -t && sudo systemctl reload nginx

Referencia completa de la configuración de Nginx

La configuración completa tras todos los cambios. Compárala con tu archivo:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

limit_req_zone $binary_remote_addr zone=webhooks:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=editor:10m rate=30r/s;

server {
    listen 80;
    listen [::]:80;
    server_name n8n.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name n8n.example.com;

    ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    server_tokens off;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://n8n.example.com; frame-ancestors 'none'" always;

    client_max_body_size 50m;

    # Rate limit webhook endpoints
    location /webhook/ {
        limit_req zone=webhooks burst=20 nodelay;
        limit_req_status 429;

        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_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    location /webhook-test/ {
        limit_req zone=webhooks burst=5 nodelay;
        limit_req_status 429;

        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;
    }

    # Restrict editor API to specific IPs
    location /rest/ {
        allow 203.0.113.50;
        allow 10.0.0.0/8;
        deny all;

        limit_req zone=editor burst=50 nodelay;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;
    }

    # Main location - editor frontend and fallback
    location / {
        limit_req zone=editor burst=50 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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;
        proxy_cache off;
        chunked_transfer_encoding off;
        proxy_connect_timeout 60s;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

¿Cómo verifico que las conexiones WebSocket funcionan en el editor de n8n?

El editor de n8n usa una conexión WebSocket en /rest/push para recibir actualizaciones de ejecución de flujos de trabajo en tiempo real. Si esta conexión falla, verás banners de "Connection lost" y el editor no se actualizará tras ejecutar flujos de trabajo.

Abre el editor de n8n en tu navegador. Abre las herramientas de desarrollador (F12), ve a la pestaña Network y filtra por "WS" (WebSocket). Deberías ver una conexión a wss://n8n.example.com/rest/push con estado 101 (Switching Protocols).

Desde la línea de comandos, prueba la actualización WebSocket:

curl -sI -H "Upgrade: websocket" -H "Connection: Upgrade" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" https://n8n.example.com/rest/push

Una respuesta 101 Switching Protocols confirma que el proxy de WebSocket funciona. Un 200 o 400 significa que las cabeceras de actualización no están llegando a n8n. Vuelve atrás y revisa la directiva map y las cabeceras de proxy Upgrade/Connection.

¿Algo salió mal?

El editor de n8n muestra "Connection lost"

El proxy de WebSocket está roto. Comprueba que:

  1. El bloque map $http_upgrade $connection_upgrade existe en la parte superior de la configuración
  2. proxy_set_header Upgrade $http_upgrade y proxy_set_header Connection $connection_upgrade están en el bloque location /
  3. proxy_http_version 1.1 está establecido (WebSocket requiere HTTP/1.1)

Revisa los logs de Nginx:

sudo journalctl -u nginx -f

"502 Bad Gateway" tras reiniciar

El contenedor de n8n no está en ejecución o no escucha en el puerto 5678:

docker ps | grep n8n
docker compose logs --tail=50 n8n

La renovación de Certbot falla

Comprueba que el temporizador está en ejecución y prueba manualmente:

sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Si la renovación falla, asegúrate de que el puerto 80 está abierto en UFW y de que el bloque del servidor HTTP sigue presente (Certbot lo necesita para el desafío HTTP-01).

"403 Forbidden" desde la IP permitida

Tu IP puede haber cambiado. Comprueba tu IP actual con curl -4 ifconfig.me y actualiza la directiva allow en la configuración de Nginx.

Limitación de tasa demasiado agresiva

Si los emisores de webhook legítimos reciben errores 429, aumenta los valores de rate y burst en tus directivas limit_req_zone y limit_req. Monitoriza primero la tasa de peticiones entrantes:

sudo tail -f /var/log/nginx/access.log | grep webhook

Próximos pasos

  • Configurar copias de seguridad y actualizaciones automáticas para tu instancia de n8n ()
  • Aprender más sobre patrones de reverse proxy con Nginx ()
  • Explorar la configuración de TLS en profundidad ()
  • Volver a la visión general de automatización de flujos de trabajo (automatización de flujos de trabajo autoalojada)

Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.

¿Listo para probarlo?

Despliega tu propio servidor en segundos. Linux, Windows o FreeBSD.

Ver planes VPS