SSD Nodes Learn
Guías Matt ConnorPor Matt Connor · Actualizado 2026-07-24

instalar n8n en VPS con Docker y HTTPS

Guía para desplegar n8n con Docker Compose y Postgres. Evita errores de WEBHOOK_URL y la pérdida de datos por no configurar correctamente la encryption-key.

Qué vas a construir

n8n es una herramienta de automatización de flujos de trabajo: un editor visual donde un trigger —un webhook, una programación, el envío de un formulario— activa una cadena de nodos que llaman a APIs, transforman datos y escriben en otros sistemas. Se ha convertido en el conector estándar para flujos de trabajo de agentes de IA porque se comunica con cualquier proveedor de modelos y bases de datos sin necesidad de escribir un servicio. Un docker run obtiene un editor funcional en dos minutos. Esta guía trata sobre el noventa por ciento restante: hacerlo duradero usando Postgres en lugar del archivo SQLite por defecto, hacerlo accesible mediante HTTPS y —la parte que casi todos configuran mal— lograr que los webhooks entreguen una URL que el mundo exterior pueda alcanzar realmente.

El stack final consiste en dos contenedores en una misma red Docker: n8n y una base de datos Postgres que almacena sus flujos de trabajo y credenciales. Un reverse proxy en el host termina la conexión TLS y la reenvía a n8n en localhost, de modo que nada se expone a internet excepto a través de ese proxy. Se encuentra junto a otros servicios en la lista de selección de self-hosting 2026.

Requisitos previos y limitaciones reales

Necesitas un VPS con al menos 1 GB de RAM; planifica usar 2 GB cuando los flujos de trabajo sean intensivos. Las ejecuciones y el runtime de Node.js consumen mucha memoria; el OOM killer deteniendo el contenedor durante la ejecución es una forma frustrante de aprender esto. Un solo vCPU es suficiente para empezar.

Necesitas un dominio o subdominio —por ejemplo n8n.example.com— con un registro A apuntando a la IP pública del VPS que resuelva antes de solicitar el certificado. Los puertos 80 y 443 deben estar abiertos hacia el proxy; el puerto 5678 de n8n no debe estar expuesto a internet. Necesitas Docker Engine y el plugin Compose; si docker compose version da error con docker: 'compose' is not a docker command, tienes el binario independiente antiguo y el plugin es sudo apt install docker-compose-plugin.

SQLite es adecuado para pruebas, Postgres para entornos de producción

La base de datos predeterminada de n8n es un archivo SQLite en /home/node/.n8n/database.sqlite. Es funcional para pruebas iniciales; si no se monta un volumen, los datos se pierden al recrear el contenedor por primera vez. La razón para migrar a Postgres no es la velocidad bruta. SQLite utiliza un bloqueo de escritura único, por lo que una instancia que ejecute varios workflows simultáneamente, o el modo queue que se requerirá eventualmente, genera SQLITE_BUSY: database is locked bajo concurrencia. Postgres no tiene esa limitación, permite copias de seguridad limpias con pg_dump y es la opción que la documentación oficial de n8n recomienda para servidores de producción. Cambiar de base de datos posteriormente requiere una migración manual de datos; si este servidor es crítico, comience con Postgres.

DNS y el firewall

Configure el registro y abra los puertos primero. Esto evita que el paso del certificado falle por un nombre que no resuelve.

dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enable

No abra el puerto 5678. El archivo compose vincula n8n a 127.0.0.1:5678 para que solo el reverse proxy del host pueda alcanzarlo. Un ufw allow 5678 anularía ese aislamiento.

El archivo Compose

Cree un directorio de trabajo y un docker-compose.yml. Este es el stack completo: dos servicios, una red privada y dos volúmenes con nombre.

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
    networks:
      - n8n_net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:2.29.10
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_PROXY_HOPS=1
      - GENERIC_TIMEZONE=Europe/London
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n_net
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

networks:
  n8n_net:

Algunas decisiones importantes. DB_POSTGRESDB_HOST=postgres es el nombre del servicio, que Docker resuelve en la red compartida; no es localhost, que dentro del contenedor n8n significa el propio n8n. El depends_on con condition: service_healthy evita que n8n intente conectar con Postgres al arrancar; sin esto, n8n inicia, no encuentra la base de datos y se cierra. El volumen con nombre n8n_data en /home/node/.n8n contiene la clave de cifrado y, en SQLite, la base de datos; es el único directorio que no debe perder. Use una versión exacta para la imagen, nunca latest; las razones se encuentran en la sección de actualizaciones más abajo.

El archivo secrets

Nunca incluya contraseñas en el archivo compose. Colóquelas en un archivo .env junto al mismo para que Compose lo lea automáticamente, y genérelas para que sean realmente aleatorias.

printf 'POSTGRES_PASSWORD=%s\n'  "$(openssl rand -hex 24)" >  .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .env

El N8N_ENCRYPTION_KEY es la cadena más importante aquí; es la clave con la que se cifra cada credencial almacenada. Defínala explícitamente en lugar de permitir que n8n genere una, ya que un valor generado por usted es un valor que puede anotar y restaurar. Una vez que n8n haya cifrado su primera credencial con esta clave, cambiarla hará que todas las credenciales sean ilegibles; por lo tanto, defínala una vez, ahora, y no vuelva a tocar esa línea.

Las variables de entorno que determinan el funcionamiento de los webhooks

Cuatro variables controlan cómo n8n se identifica ante el exterior. Configurarlas incorrectamente es la causa principal de las consultas de soporte de n8n.

  • N8N_HOST es el hostname público, n8n.example.com. Si se deja el valor predeterminado localhost detrás de un proxy, el editor intentará cargar su propia API desde localhost en su navegador, lo cual fallará.
  • N8N_PROTOCOL=https indica a n8n que se utiliza TLS, por lo que marca su cookie de sesión Secure y genera URLs con https://.
  • N8N_PORT=5678 es el puerto en el que n8n escucha dentro del contenedor. No es el puerto público; el proxy utiliza el 443.
  • WEBHOOK_URL=https://n8n.example.com/ es la variable crítica. n8n genera las direcciones de webhook que usted pega en Stripe, GitHub o cualquier cliente externo basándose en estos valores. Si no está configurada o es incorrecta, n8n usará N8N_HOST:N8N_PORT y entregará https://n8n.example.com:5678/webhook/... o, peor aún, http://localhost:5678/webhook/.... Estos valores se muestran sin error y parecen válidos, pero son inalcanzables desde internet, por lo que las peticiones del cliente nunca llegan. Configure el URL base público exacto con la barra diagonal final y verifique que el nodo de webhook muestre un URL sin puerto.

N8N_PROXY_HOPS=1 indica al servidor Express de n8n que confíe en un proxy frontal. Esto permite que el límite de tasa (rate-limiting) y cualquier función que lea la IP del cliente vean la dirección real en lugar de la del proxy. Una variable que no debe configurar aquí es N8N_RUNNERS_ENABLED: los task runners —n8n ejecutando la lógica de Code-node en un proceso sandboxed separado— son el valor predeterminado desde la versión 1.69 y son obligatorios desde la línea 2.x que define esta guía; por tanto, la opción de activación manual antigua está obsoleta. Si la configura, n8n solo registrará un aviso indicándole que debe eliminarla.

Primer inicio

docker compose up -d
docker compose ps
docker compose logs -f n8n

Un primer arranque exitoso termina con una línea Editor is now accessible via:, con una línea n8n ready on ..., port 5678 justo arriba. docker compose ps debe mostrar ambos contenedores Up, con postgres marcado como (healthy). Si n8n entra en un bucle Restarting, revise los logs; generalmente se debe a la conexión de la base de datos o a los permisos del volumen descritos a continuación.

TLS con un proxy inverso

n8n utiliza HTTP estándar en el puerto 5678; un componente externo debe gestionar el cierre de la conexión HTTPS. Existen dos opciones principales.

Si ya tiene varios contenedores en ejecución, coloque n8n detrás de un proxy inverso Traefik que emite certificados TLS automáticamente usando un par de labels; Traefik solicita y renueva el certificado por usted.

Si esta es la única aplicación en el servidor, un virtual host de nginx con un certificado Let's Encrypt es más sencillo. Utilice la configuración de TLS de Certbot y nginx para Ubuntu 24.04 para obtener el certificado, y luego use este bloque de servidor:

server {
    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;

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600;
        client_max_body_size 16m;
    }
}

Los headers Upgrade y Connection "upgrade" son obligatorios. n8n envía actualizaciones de ejecución en tiempo real al editor mediante WebSocket; sin estas dos líneas, la página de inicio de sesión carga pero se bloquea con un aviso de pérdida de conexión. proxy_read_timeout 3600 evita que las ejecuciones de larga duración se corten al alcanzar el límite predeterminado de 60 segundos de nginx. El header X-Forwarded-Proto $scheme es el complemento de N8N_PROXY_HOPS=1: indica a n8n que la petición original fue HTTPS aunque el proxy se comunique por HTTP, evitando que n8n considere la conexión como insegura y rechace su propia cookie.

Su primer flujo de trabajo para ponerlo en marcha

Abra https://n8n.example.com/, cree la cuenta de propietario (siguiente sección) y construya el flujo de trabajo más simple para verificar que la ruta funciona: una entrada por webhook, una llamada HTTP y una respuesta de salida.

  1. Añada un nodo Webhook. Configure el método en POST y una ruta como hello. Se mostrarán dos URLs: una Test URL y una Production URL; estas últimas causan la mitad de los errores de tipo "mi webhook no funciona". La Test URL responde a una sola llamada y solo mientras tenga pulsado Listen for test event; después de eso, expira. La Production URL responde siempre que el flujo de trabajo esté Active.
  2. Añada un nodo HTTP Request después del webhook, apuntando a cualquier API JSON pública; un GET a https://api.github.com/zen devuelve una cadena de una sola línea, lo cual es suficiente.
  3. Añada un nodo Respond to Webhook y configure la opción Respond del nodo Webhook como "Using Respond to Webhook node" para que el cliente reciba la salida del nodo HTTP.
  4. Active el flujo de trabajo (Active, arriba a la derecha) y ejecútelo: curl -X POST https://n8n.example.com/webhook/hello. Debería recibir la línea de texto de vuelta — entrada POST, llamada a la API y respuesta de salida; este es el esquema de la mayoría de las automatizaciones reales.

Una variante programada sustituye el nodo Webhook por un Schedule Trigger y llama a un endpoint de un modelo en su lugar; usar Ollama ejecutándose en el mismo VPS es una forma eficiente de crear un resumidor nocturno.

Gestión de usuarios, no basic auth

Las guías antiguas de n8n recomiendan configurar N8N_BASIC_AUTH_ACTIVE=true. Esas variables se eliminaron en n8n 1.0 y ya no tienen efecto. La autenticación actual se basa en la owner account: la primera vez que cargas el editor, n8n requiere que crees un usuario propietario con email y contraseña. Este paso es obligatorio; no existe el modo anónimo. Crea la cuenta inmediatamente después del primer arranque, antes de compartir la URL: entre docker compose up y el envío de ese primer formulario, cualquier persona que acceda a la instancia puede reclamarla. Añadir una capa de basic-auth mediante un reverse-proxy es un refuerzo razonable, pero actúa como un segundo factor, no como la autenticación real.

Backups: primero la clave de cifrado, luego la base de datos

Es necesario realizar dos tipos de copias de seguridad, y no tienen el mismo nivel de importancia.

El N8N_ENCRYPTION_KEY. Todas las credenciales almacenadas en n8n —tokens de API, contraseñas de bases de datos, secretos de OAuth— se cifran en reposo con esta clave. Los workflows en Postgres no funcionarán sin ella: si restauras la base de datos en un servidor nuevo con una clave distinta, n8n no podrá descifrar ninguna credencial; no hay forma de recuperarlas ni de reiniciarlas. El archivo .env contiene la clave; cópialo fuera del servidor —un gestor de contraseñas es ideal— el mismo día que lo crees. Esta es la copia de seguridad más importante.

La base de datos Postgres, para los workflows, el historial de ejecuciones y las propias credenciales cifradas:

docker compose exec -T postgres pg_dump -U n8n -d n8n \
  | gzip > n8n-db-$(date +%F).sql.gz

Ejecuta este comando de forma programada y copia el dump fuera del servidor. Para restaurar en un VPS nuevo: inicia el stack una vez para que la base de datos exista, detén n8n, carga el dump con psql, coloca la misma N8N_ENCRYPTION_KEY en .env y arranca n8n. La combinación de la misma clave y el dump genera una instancia operativa; una clave nueva resulta en workflows que no pueden usar ninguna credencial.

Actualizaciones: fijar la etiqueta (tag)

El archivo compose fija n8nio/n8n:2.29.10 en lugar de latest por diseño. n8n lanza una nueva versión minor cada semana y ocasionalmente cambia el esquema de la base de datos o el comportamiento de los nodos entre versiones; por tanto, latest significa que una descarga automática puede entregar una versión que migre su base de datos en cuanto se inicie. Fije una versión, lea las notas de la versión antes de actualizar —n8n indica los cambios disruptivos allí— y actualice de forma deliberada:

docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8n

Los saltos de versión mayor son donde esto es más crítico. La línea 2.0, por ejemplo, cambió N8N_BLOCK_ENV_ACCESS_IN_NODE a true por defecto, por lo que cualquier nodo Code que leyera process.env perderá el acceso silenciosamente hasta que lo restablezca a false; esa misma versión comenzó a aplicar permisos estrictos en el archivo de configuración. Lea la página de cambios disruptivos de la 2.0 antes de cambiar de una versión mayor a otra. n8n ejecuta automáticamente cualquier migración de base de datos necesaria al iniciar; es precisamente por eso que el pg_dump previo a la actualización no es opcional. Debido a que las credenciales se guardan cifradas con una clave en .env y los datos residen en Postgres, los contenedores son desechables: actualice reemplazándolos y revierta los cambios fijando la etiqueta anterior y restaurando el volcado (dump).

Modos de fallo y los mensajes que verá

The requested webhook "POST hello" is not registered. Un error 404 al llamar a un webhook cuyo workflow no está en estado Active, o al llamar a la ruta de prueba cuando no hay nadie escuchando. Las rutas de prueba (/webhook-test/...) responden solo mientras tenga activado "Listen for test event"; las rutas de producción (/webhook/...) responden solo cuando el interruptor del workflow está encendido. El error hermano This webhook is not registered for GET requests. Did you mean to make a POST request? indica que el método es incorrecto: el nodo espera POST y usted envió GET.

La URL del webhook muestra un :5678 o localhost. El nodo muestra https://n8n.example.com:5678/webhook/... o http://localhost:5678/.... WEBHOOK_URL no está configurado o es incorrecto, por lo que n8n construyó la dirección usando N8N_HOST:N8N_PORT en lugar de su base pública. Configure WEBHOOK_URL=https://n8n.example.com/, recree el contenedor con docker compose up -d y el puerto desaparecerá.

There was a problem loading init data en el navegador. El editor cargó pero no puede alcanzar su propia API de backend. Detrás de un proxy, esto es casi siempre un N8N_HOST o WEBHOOK_URL incorrecto, un proxy que no incluye los headers de WebSocket Upgrade, o un N8N_PROTOCOL que no coincide con su método de conexión. Confirme las cuatro variables públicas y que el proxy reenvíe Upgrade y Connection.

password authentication failed for user "n8n" en los logs, con el contenedor reiniciándose. La contraseña que envía n8n no coincide con la que se usó al inicializar la base de datos. El problema: Postgres lee POSTGRES_PASSWORD solo cuando inicializa un directorio de datos vacío. Inicie el stack una vez, luego cambie POSTGRES_PASSWORD en .env, y el volumen postgres_data existente seguirá teniendo la contraseña antigua. Restablézcala al valor original o, si no tiene datos que conservar, haga docker compose down y docker volume rm al volumen de postgres y levántelo de nuevo.

EACCES: permission denied, open '/home/node/.n8n/config' al iniciar. n8n se ejecuta como el usuario node (UID 1000) y no puede escribir en su directorio de configuración. Esto afecta a quienes usan un bind-mount de una carpeta del host (./n8n_data:/home/node/.n8n) propiedad de root. Use el volumen con nombre mostrado arriba o, si insiste en usar un bind mount, ejecute sudo chown -R 1000:1000 ./n8n_data primero.

Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. A partir de la línea 2.x, n8n aplica 0600 en ese archivo de configuración por defecto y lo corrige automáticamente al arrancar; esta línea de log significa que ya corrigió el modo, comúnmente después de un bind mount o tras restaurar un archivo copiado con permisos laxos. No es necesaria ninguna acción; configure N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false solo si su sistema de archivos realmente no puede soportar permisos.

Mismatching encryption keys — la línea completa indica que la clave de cifrado en el archivo de configuración /home/node/.n8n/config no coincide con la N8N_ENCRYPTION_KEY de su entorno. La clave en su entorno es distinta a la que n8n escribió en su volumen de datos en una ejecución anterior; esto ocurre mayormente porque n8n generó una clave aleatoria en un arranque previo cuando la variable no estaba definida, y luego usted configuró una distinta. Coloque la clave original de nuevo en .env o, solo si realmente no tiene credenciales guardadas que valga la pena conservar, elimine el archivo config dentro del volumen n8n_data y deje que n8n lo regenere, aceptando que las credenciales existentes dejarán de ser legibles.

Un banner de inicio de sesión sobre cookies seguras: Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. Usted configuró N8N_PROTOCOL=https pero accedió a n8n mediante HTTP simple, generalmente al usar la IP y el puerto directamente en lugar del proxy HTTPS. Acceda mediante https://n8n.example.com/. Solo si realmente no puede usar HTTPS debe configurar N8N_SECURE_COOKIE=false, y nunca en un servidor con acceso a internet.

Para integrar un modelo de lenguaje en esos workflows, vea building AI workflows with Claude and n8n.

FAQ

¿Debo usar SQLite o Postgres para n8n?

SQLite (el predeterminado) es adecuado para probar n8n o para una instancia personal que ejecute un solo workflow a la vez. Cambie a Postgres para cualquier entorno de producción: el bloqueo de escritura único de SQLite causa database is locked bajo concurrencia, y Postgres permite realizar copias de seguridad limpias con pg_dump. La migración posterior es manual, así que si el servidor es crítico, comience con Postgres.

¿Por qué mis webhooks de n8n nunca se ejecutan?

Casi siempre es WEBHOOK_URL. Si no está configurado o es incorrecto, n8n genera direcciones de webhook basadas en N8N_HOST:N8N_PORT —a menudo con un :5678 o localhost— que parecen válidas pero son inalcanzables desde internet; por tanto, las peticiones del cliente nunca llegan. Configure WEBHOOK_URL=https://n8n.example.com/ y confirme que el nodo muestre una URL sin puerto. La segunda causa es llamar a un webhook cuyo workflow no esté en estado Active, lo que devuelve The requested webhook ... is not registered..

¿Qué debo respaldar en n8n?

Dos elementos. El N8N_ENCRYPTION_KEY de su archivo .env, ya que cada credencial almacenada está cifrada con este y perderlo impide el descifrado permanente; cópielo fuera del servidor el mismo día que lo cree. Y un pg_dump de la base de datos Postgres para los workflows, el historial y las credenciales. La restauración requiere ambos: la misma clave más el dump.

¿Cómo pongo n8n tras HTTPS?

n8n sirve HTTP sin cifrar en el puerto 5678; un reverse proxy delante termina la conexión TLS. Vincule n8n a 127.0.0.1:5678 para que solo el proxy pueda alcanzarlo, luego use Traefik con certificados automáticos o nginx con un certificado de Let's Encrypt. Configure N8N_PROTOCOL=https y WEBHOOK_URL=https://your-host/, y asegúrese de que el proxy reenvíe los headers de WebSocket Upgrade o el editor se bloqueará.

¿Cómo actualizo n8n de forma segura?

Fije una etiqueta de imagen específica en lugar de latest, realice un pg_dump primero porque n8n ejecuta migraciones automáticamente al iniciar, lea las notas de la versión para detectar cambios disruptivos, luego actualice la etiqueta y ejecute docker compose pull n8n && docker compose up -d n8n. El contenedor es desechable, así que para revertir los cambios fije la etiqueta anterior y restaure el dump previo a la actualización.