SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-27

Instalar n8n en un VPS con Docker y HTTPS

Despliega n8n con Docker Compose, Postgres y un proxy inverso HTTPS. Evita los fallos de WEBHOOK_URL y N8N_ENCRYPTION_KEY, con errores concretos.

Qué va a construir

n8n es una herramienta de automatización de flujos de trabajo: un editor visual en el que un desencadenador, un webhook, una programación o el envío de un formulario inicia una cadena de nodos que llaman a APIs, transforman datos y escriben en otros sistemas. Se ha convertido en el componente de integración predeterminado para los flujos de trabajo con agentes de IA porque se comunica con todos los proveedores de modelos y bases de datos sin que tenga que escribir un servicio. Un docker run permite tener un editor funcional en dos minutos. Esta guía trata sobre el otro noventa por ciento: hacerlo persistente con Postgres en lugar del archivo SQLite predeterminado, acceder a él mediante HTTPS y, que es lo que casi todo el mundo configura mal, hacer que los webhooks proporcionen una URL a la que el exterior pueda acceder realmente.

La pila terminada consta de dos contenedores en una misma red de Docker: n8n y una base de datos Postgres que almacena sus flujos de trabajo y credenciales. Un reverse proxy en el host termina TLS y reenvía las peticiones a n8n en localhost, de modo que nada queda expuesto a Internet salvo a través de ese proxy. Se ejecuta junto con los demás servicios de la selección de self-hosting de 2026.

Requisitos previos y límites reales

Necesita un VPS con al menos 1 GB de RAM. Planifique 2 GB cuando los workflows empiecen a ejecutar tareas reales, porque las ejecuciones y el runtime de Node.js consumen memoria, y que el OOM killer detenga el contenedor a mitad de una ejecución es una forma muy desagradable de descubrirlo. Un solo vCPU es suficiente para empezar. Si este servidor también ejecutará otro servicio más exigente, dimensione el VPS para ese servicio en primer lugar: una biblioteca de fotos suele ser la causa, y los requisitos mínimos reales de RAM de PhotoPrism e Immich superan ampliamente los de n8n. Lo mismo ocurre con un servidor multimedia: un servidor Jellyfin y una interfaz web para explorarlo, como Halcyon, que reconstruye la biblioteca como un videoclub de los años 90, consumirán la RAM y la capacidad disponible para la transcodificación mucho antes de que n8n lo note.

Necesita un dominio o subdominio, por ejemplo n8n.example.com, con un registro A que apunte a la IP pública del VPS y que resuelva antes de solicitar un certificado. Los puertos 80 y 443 deben estar abiertos para el proxy. El puerto propio de n8n, 5678, no debe quedar expuesto a Internet. Necesita Docker Engine y el plugin de Compose. Si docker compose version devuelve un error con docker: 'compose' is not a docker command, tiene el binario independiente antiguo. El plugin es sudo apt install docker-compose-plugin.

SQLite sirve para una prueba; use Postgres para cualquier instalación de la que dependa

La base de datos predeterminada de n8n es un archivo SQLite en /home/node/.n8n/database.sqlite. Para hacer una prueba rápida sirve: no monte ningún volumen y perderá los datos al recrear el contenedor por primera vez. Esa también es una lección. El motivo para pasar a Postgres no es la velocidad bruta. SQLite mantiene un único bloqueo de escritura, por lo que una instancia que ejecuta varios flujos de trabajo a la vez, o el modo de cola que probablemente necesitará más adelante, genera SQLITE_BUSY: database is locked cuando hay concurrencia. Postgres no tiene ese límite, permite realizar copias de seguridad correctamente con pg_dump y es la base de datos que presupone la documentación de n8n para un servidor del que depende. Cambiar más adelante implica migrar los datos manualmente. Si este servidor es importante, empiece con Postgres.

DNS y el firewall

Apunte el registro y abra primero los puertos para que el paso posterior del certificado no falle porque el nombre 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 5678. El archivo de Compose enlaza n8n con 127.0.0.1:5678, de modo que sólo el reverse proxy del host puede acceder a él, y un ufw allow 5678 anularía ese aislamiento.

El archivo de Compose

Cree un directorio de trabajo y un docker-compose.yml. Esta es toda la pila: 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:

Conviene indicar claramente algunas decisiones. DB_POSTGRESDB_HOST=postgres es el nombre del servicio, que Docker resuelve en la red compartida, no localhost, que dentro del contenedor de n8n significa el propio n8n. El depends_on con condition: service_healthy evita que n8n intente conectarse a Postgres antes de que esté listo durante el arranque; sin él, n8n se inicia, no encuentra la base de datos y termina. El volumen con nombre n8n_data en /home/node/.n8n contiene la clave de cifrado y, con SQLite, la base de datos. Es el único directorio que no debe perder. Fije la imagen a una versión exacta, nunca latest; los motivos se explican en la sección sobre actualizaciones que aparece más abajo.

El archivo de secretos

Nunca coloque contraseñas en el archivo compose. Colóquelas en un archivo .env junto a él, que Compose lee 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

N8N_ENCRYPTION_KEY es la cadena más importante de esta configuración. Es la clave con la que se cifran todas las credenciales almacenadas. Defínala explícitamente en lugar de permitir que n8n la genere, porque un valor generado por usted se puede anotar y restaurar. Cuando n8n haya cifrado la primera credencial con esta clave, cambiarla hará que ninguna credencial se pueda descifrar. Defínala una sola vez, ahora, y no vuelva a modificar esa línea.

Las variables de entorno que determinan si funcionan los webhooks

Cuatro variables controlan cómo se presenta n8n ante el exterior. Configurarlas mal es la principal causa de consultas al soporte de n8n.

  • N8N_HOST es el nombre de host público, n8n.example.com. Si se deja con el valor predeterminado localhost detrás de un proxy, el editor intenta cargar su propia API desde localhost en el navegador, lo que falla.
  • N8N_PROTOCOL=https indica a n8n que se sirve mediante TLS. Por eso marca su cookie de sesión como Secure y genera URLs https://.
  • N8N_PORT=5678 es el puerto en el que n8n escucha dentro del contenedor. No es el puerto público; el proxy gestiona el 443.
  • WEBHOOK_URL=https://n8n.example.com/ es la variable problemática. n8n imprime las direcciones de webhook que se pegan en Stripe, GitHub o cualquier cliente externo. Las genera a partir de estos valores. Si no está definida o es incorrecta, n8n usa N8N_HOST:N8N_PORT y proporciona https://n8n.example.com:5678/webhook/... o, peor aún, http://localhost:5678/webhook/.... Las muestra sin ningún error, parecen válidas, pero no son accesibles desde Internet. Por eso las peticiones del cliente nunca llegan y el fallo pasa inadvertido. Establécela con la URL base pública exacta y la barra final. Después, confirma que el nodo de webhook muestra una URL sin puerto.

N8N_PROXY_HOPS=1 indica al servidor Express de n8n que confíe en un proxy situado delante de él. Así, la limitación de tasa y cualquier función que lea la IP del cliente ven la dirección real en lugar de la del proxy. Una variable que no debes definir aquí es N8N_RUNNERS_ENABLED. Los ejecutores de tareas, que ejecutan la lógica de los nodos Code de n8n en un proceso independiente y aislado, son el comportamiento predeterminado desde 1.69 y son obligatorios en la línea 2.x fijada por esta guía. Por eso, la antigua activación opcional está obsoleta. Si la defines ahora, n8n sólo registra un aviso que indica que debes eliminarla.

Primer arranque

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

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

TLS con un proxy inverso

n8n utiliza HTTP sin cifrar en el puerto 5678; un componente situado delante termina la conexión HTTPS. Hay dos opciones adecuadas.

Si ya ejecuta varios contenedores, coloque n8n detrás de un proxy inverso Traefik que emita certificados TLS automáticamente mediante un par de etiquetas. Traefik solicitará y renovará el certificado por usted.

Si esta es la única aplicación del servidor, un host virtual de nginx con un certificado de Let's Encrypt es más sencillo. Use la configuración de TLS de Certbot y nginx para Ubuntu 24.04 para obtener el certificado y, después, 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;
    }
}

Las cabeceras Upgrade y Connection "upgrade" son obligatorias. n8n envía las actualizaciones de ejecución en tiempo real al editor mediante un WebSocket. Sin esas dos líneas, la página de inicio de sesión se carga y después se bloquea con un aviso de pérdida de conexión. proxy_read_timeout 3600 evita que nginx corte las ejecuciones de larga duración al alcanzar su valor predeterminado de 60 segundos. La cabecera X-Forwarded-Proto $scheme complementa a N8N_PROXY_HOPS=1: indica a n8n que la solicitud original utilizó HTTPS, aunque el proxy se conecte mediante HTTP sin cifrar. Así, n8n no considera que la conexión sea insegura ni rechaza su propia cookie.

Tu primer flujo de trabajo, para ponerlo en marcha

Abre https://n8n.example.com/, crea la cuenta de propietario (en la sección siguiente) y crea el flujo más pequeño que demuestre que el recorrido funciona: entra un webhook, se realiza una llamada HTTP y sale una respuesta.

  1. Añade un nodo Webhook. Establece el método en POST y una ruta como hello. Muestra dos URL: una Test URL y una Production URL. Esto causa la mitad de los informes de «mi webhook no funciona». La Test URL responde a una llamada y sólo mientras hayas seleccionado Listen for test event; después caduca. La Production URL responde cuando el flujo de trabajo está Active.
  2. Añade después un nodo HTTP Request, dirigido a cualquier API JSON pública. Una solicitud GET a https://api.github.com/zen devuelve una cadena de una línea, suficiente para esta prueba.
  3. Añade un nodo Respond to Webhook y establece la opción Respond del nodo Webhook en "Using Respond to Webhook node" para que el autor de la llamada reciba la salida del nodo HTTP.
  4. Activa el flujo de trabajo con Active (arriba a la derecha) y llámalo: curl -X POST https://n8n.example.com/webhook/hello. Deberías recibir de vuelta la línea zen: entra una solicitud POST, se realiza la llamada a la API y sale la respuesta. Esta es la estructura 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 modelo. Uno autoalojado, como Ollama ejecutándose en el mismo VPS, es una forma ordenada de crear un resumidor nocturno.

Gestión de usuarios, no autenticación básica

Las guías antiguas de n8n indican que se debe configurar N8N_BASIC_AUTH_ACTIVE=true. Esas variables se eliminaron en n8n 1.0 y ahora no tienen ningún efecto. La autenticación actual usa la cuenta del propietario: la primera vez que se carga el editor, n8n solicita crear una cuenta del propietario con correo electrónico y contraseña. Este control es obligatorio y no existe un modo anónimo. Créela inmediatamente después del primer arranque, antes de entregar la URL a otras personas: entre docker compose up y el primer envío de ese formulario, cualquier persona que acceda a la instancia puede reclamarla. Añadir una capa de autenticación básica en el proxy inverso es una medida adicional razonable, pero constituye un segundo factor y no la autenticación principal. La cuenta del propietario y todo lo demás descrito en esta guía funcionan con la edición comunitaria gratuita. Si más adelante necesita usuarios adicionales con roles detallados o SSO, conviene leer qué funciones de n8n requieren una licencia de pago antes de planificar su implementación.

Copias de seguridad: primero la clave de cifrado y después la base de datos

Hay que hacer copias de seguridad de dos elementos, pero no se pueden sustituir con la misma facilidad.

La N8N_ENCRYPTION_KEY. Todas las credenciales que almacena n8n, incluidos los tokens de API, las contraseñas de la base de datos y los secretos de OAuth, se cifran en reposo con esta clave. Los flujos de trabajo de Postgres no sirven sin ella: si restaura la base de datos en otro equipo con una clave diferente, n8n no podrá descifrar ninguna credencial. No habrá recuperación ni restablecimiento. El archivo .env contiene la clave. Cópielo fuera del servidor en cuanto lo cree. Una entrada de un gestor de contraseñas es una opción adecuada. Esta es la copia de seguridad realmente importante.

La base de datos de Postgres, que contiene los flujos de trabajo, 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

Ejecútelo según una programación y copie el volcado fuera del equipo. Para restaurarlo en un VPS nuevo: inicie la pila una vez para que se cree la base de datos, detenga n8n, vuelva a cargar el volcado con psql, coloque el mismo N8N_ENCRYPTION_KEY en .env y reinicie n8n. La misma clave más el volcado producen una instancia operativa. Una clave nueva deja los flujos de trabajo sin ninguna credencial utilizable.

Actualizaciones: fije la etiqueta

El archivo compose fija n8nio/n8n:2.29.10 en lugar de latest de forma intencionada. n8n publica una nueva versión menor la mayoría de las semanas y, en ocasiones, cambia el esquema de la base de datos o el comportamiento de los nodos entre versiones. Por eso, latest permite que una extracción desatendida le proporcione una compilación que migre la base de datos en cuanto se inicie. Fije una versión, lea las notas de la versión antes de actualizar; n8n indica allí los cambios incompatibles. Actualice de forma planificada:

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 principal son donde esto resulta más importante. La línea 2.0, por ejemplo, cambió N8N_BLOCK_ENV_ACCESS_IN_NODE a true de forma predeterminada. Por tanto, cualquier nodo Code que leyera process.env pierde el acceso sin avisar hasta que lo cambie de nuevo a false. Esa misma versión empezó a aplicar permisos estrictos al archivo de configuración. Lea la página de cambios incompatibles de 2.0 antes de pasar a otra versión principal. n8n ejecuta automáticamente las migraciones necesarias de la base de datos al iniciarse. Precisamente por eso, el pg_dump previo a la actualización no es opcional. Como las credenciales se almacenan cifradas con una clave en .env y los datos se almacenan en Postgres, los contenedores son desechables: actualice reemplazándolos y revierta fijando la etiqueta anterior y restaurando el volcado.

Modos de fallo y cadenas que verá

The requested webhook "POST hello" is not registered. Un 404 al llamar a un webhook cuyo flujo de trabajo no está Active, o al llamar a la ruta de prueba cuando no hay nadie escuchando. Las rutas de prueba (/webhook-test/...) responden sólo mientras haya hecho clic en "Listen for test event"; las rutas de producción (/webhook/...) responden sólo cuando el interruptor del flujo de trabajo está activado. El código hermano This webhook is not registered for GET requests. Did you mean to make a POST request? significa 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á definido o es incorrecto, por lo que n8n generó la dirección a partir de N8N_HOST:N8N_PORT en lugar de la base pública. Defina WEBHOOK_URL=https://n8n.example.com/, vuelva a crear el contenedor con docker compose up -d y el puerto desaparecerá.

There was a problem loading init data en el navegador. El editor se cargó, pero no puede acceder a su propia API de backend. Detrás de un proxy, casi siempre se debe a un N8N_HOST o WEBHOOK_URL incorrecto, a que el proxy no envía las cabeceras Upgrade de WebSocket o a que N8N_PROTOCOL no coincide con la forma en que se conecta. Confirme las cuatro variables públicas y que el proxy reenvíe Upgrade y Connection.

password authentication failed for user "n8n" en los registros, mientras el contenedor se reinicia. La contraseña que n8n envía no coincide con la que se usó para inicializar la base de datos. El problema es que Postgres sólo lee POSTGRES_PASSWORD cuando inicializa un directorio de datos empty. Inicie la pila una vez, cambie después POSTGRES_PASSWORD en .env y el volumen postgres_data existente seguirá almacenando la contraseña antigua. Restáurela al valor original o, si no necesita conservar datos, docker compose down y docker volume rm el volumen de postgres; después inicie la pila desde cero.

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 ocurre con frecuencia cuando se monta una carpeta del host (./n8n_data:/home/node/.n8n) cuyo propietario es root. Use el volumen con nombre mostrado arriba o, si debe usar un bind mount, ejecute primero sudo chown -R 1000:1000 ./n8n_data.

Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. Desde la línea 2.x, n8n aplica 0600 a ese archivo de configuración de forma predeterminada y lo corrige automáticamente durante el arranque. Esta línea de registro significa que ya corrigió los permisos, normalmente después de un bind mount o de restaurar una copia que devolvió el archivo con permisos demasiado abiertos. No es necesario hacer nada; defina N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false sólo si su sistema de archivos no puede admitir permisos de forma real.

Mismatching encryption keys La línea completa indica que la clave de cifrado del archivo de configuración /home/node/.n8n/config no coincide con N8N_ENCRYPTION_KEY en su entorno. La clave del entorno difiere de la que n8n escribió en su volumen de datos durante una ejecución anterior. Esto ocurre normalmente porque n8n generó una clave aleatoria en un arranque anterior, cuando la variable no estaba definida, y después usted definió otra. Vuelva a poner la clave original en .env o, sólo si realmente no tiene credenciales almacenadas que conservar, elimine el archivo config dentro del volumen n8n_data y permita que n8n lo regenere. Las credenciales existentes dejarán de poder leerse.

Un aviso 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. Definió N8N_PROTOCOL=https, pero accedió a n8n mediante HTTP sin cifrar, normalmente al usar directamente la IP y el puerto en lugar del proxy HTTPS. Acceda mediante https://n8n.example.com/. Sólo si realmente no puede usar HTTPS debe definir N8N_SECURE_COOKIE=false, y nunca en un servidor expuesto a Internet.

Para integrar un modelo de lenguaje en esos flujos de trabajo, consulte cómo crear flujos de trabajo de IA con Claude y n8n.

FAQ

¿Debo usar SQLite o Postgres para n8n?

SQLite (la opción predeterminada) es suficiente para probar n8n y para una instancia personal que ejecute un flujo de trabajo a la vez. Use Postgres para cualquier instancia de la que dependa: el bloqueo de escritura único de SQLite genera database is locked con concurrencia, mientras que Postgres permite realizar copias de seguridad correctamente con pg_dump. La migración posterior es manual, por lo que, si el servidor es importante, empiece con Postgres.

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

Casi siempre se debe a WEBHOOK_URL. Si no está definido o es incorrecto, n8n muestra direcciones de webhook construidas a partir de N8N_HOST:N8N_PORT, a menudo con un :5678 o localhost, que parecen válidas pero no son accesibles desde Internet. Por eso, las peticiones del emisor nunca llegan. Defina 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 flujo de trabajo no está activado, lo que devuelve The requested webhook ... is not registered.

¿Qué debo incluir en la copia de seguridad de n8n?

Dos cosas. El N8N_ENCRYPTION_KEY de su archivo .env, porque todas las credenciales almacenadas se cifran con él y, si se pierde, dejan de poder descifrarse de forma permanente. Cópielo fuera del servidor el mismo día en que lo cree. También necesita un pg_dump de la base de datos de Postgres con los flujos de trabajo, el historial y las credenciales. Una restauración requiere ambos elementos: la misma clave y el volcado.

¿Cómo coloco n8n detrás de HTTPS?

n8n sirve HTTP sin cifrar en el puerto 5678; un reverse proxy situado delante gestiona la terminación TLS. Enlace n8n a 127.0.0.1:5678 para que sólo el proxy pueda acceder a él. Después, use Traefik con certificados automáticos o nginx con un certificado de Let's Encrypt. Defina N8N_PROTOCOL=https y WEBHOOK_URL=https://your-host/, y asegúrese de que el proxy reenvíe las cabeceras Upgrade de WebSocket; de lo contrario, el editor se bloquea.

¿Cómo actualizo n8n de forma segura?

Fije una etiqueta de imagen específica en lugar de latest. Haga primero un pg_dump, porque n8n ejecuta las migraciones automáticamente al iniciarse. Lea las notas de la versión para conocer los cambios incompatibles. Después, actualice la etiqueta y ejecute docker compose pull n8n && docker compose up -d n8n. El contenedor es desechable, por lo que puede revertir el cambio fijando la etiqueta anterior y restaurando el volcado previo a la actualización.