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 enableNo 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 .envN8N_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 n8n se presenta ante el exterior. Configurarlas mal es la principal consulta de soporte sobre n8n.
N8N_HOSTes el nombre de host público,n8n.example.com. Si se deja con el valor predeterminadolocalhostdetrás de un proxy, el editor intenta cargar su propia API desdelocalhosten el navegador, y la carga falla.N8N_PROTOCOL=httpsindica a n8n que se sirve mediante TLS. Por eso marca su cookie de sesión comoSecurey genera URLhttps://.N8N_PORT=5678es 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 que suele causar problemas. n8n genera las direcciones de webhook que se pegan en Stripe, GitHub o cualquier cliente externo a partir de estos valores. Si no está definida o es incorrecta, n8n usaN8N_HOST:N8N_PORTy proporcionahttps://n8n.example.com:5678/webhook/...o, peor aún,http://localhost:5678/webhook/.... Las imprime sin mostrar ningún error. Parecen válidas, pero no son accesibles desde Internet, por lo que las solicitudes del cliente nunca llegan y el fallo pasa inadvertido. Defínala con la URL base pública exacta y la barra final. Después, confirme que el nodo webhook muestra una URL sin puerto.
N8N_PROXY_HOPS=1 indica al servidor Express de n8n que confíe en un proxy situado delante. 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. Hay una variable que deliberadamente no se define aquí: N8N_RUNNERS_ENABLED. Los ejecutores de tareas permiten que n8n ejecute la lógica de los nodos Code en un proceso separado del proceso principal. Fueron una función opcional durante toda la serie 1.x. En esa serie, la variable tenía el valor predeterminado false y era necesario establecerla en true. n8n 2.0 los activó de forma predeterminada. Por eso, en la serie 2.x esta guía fija la variable como obsoleta y la documentación de n8n indica que ya no es necesario definirla.
Primer arranque
docker compose up -d
docker compose ps
docker compose logs -f n8nUn 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 funcionamiento
Abra https://n8n.example.com/, cree la cuenta propietaria (en la sección siguiente) y construya el flujo más pequeño que demuestre que el recorrido funciona: entrada mediante webhook, llamada HTTP y respuesta.
- Añada un nodo Webhook. Establezca el método en
POSTy una ruta comohello. Muestra dos URL: una Test URL y una Production URL. Esto explica muchos de los informes de «mi webhook no funciona». La Test URL responde a una sola llamada y sólo mientras haya hecho clic en Listen for test event; después caduca. La Production URL se registra cuando publica el flujo y responde a partir de ese momento. - Añada después un nodo HTTP Request y configúrelo para una API JSON pública cualquiera. Una solicitud GET a
https://api.github.com/zendevuelve una cadena de una línea, que es suficiente. - Añada un nodo Respond to Webhook y establezca la opción Respond del nodo Webhook en "Using Respond to Webhook node" para que el cliente reciba de vuelta la salida del nodo HTTP.
- Haga clic en Publish (arriba a la derecha) y llámelo:
curl -X POST https://n8n.example.com/webhook/hello. Debería recibir de vuelta la línea zen: entrada POST, llamada a la API y salida de la respuesta. Esta es la estructura de la mayoría de las automatizaciones reales. Las guías antiguas indican que debe activar aquí un interruptor Active; n8n 2.0 sustituyó ese interruptor por Publish y Unpublish.
Una variante programada sustituye el nodo Webhook por un Schedule Trigger y llama a un endpoint de modelo. Uno autohospedado, 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.gzEjecú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 casi todas las semanas y, ocasionalmente, cambia el esquema de la base de datos o el comportamiento de los nodos entre versiones. Por eso, latest implica que una extracción desatendida puede proporcionarle 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. Después, actualice de forma controlada:
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 n8nLos saltos de versión principal son el caso en el que esto resulta más importante. Por ejemplo, la línea 2.0 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 mostrar errores hasta que lo cambie de nuevo a false. La misma versión empezó a aplicar permisos estrictos al archivo de configuración, habilitó task runners de forma predeterminada y sustituyó el interruptor Activate de los workflows por Publish. 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, pg_dump antes de la actualización no es opcional. Las credenciales se almacenan cifradas con una clave en .env y los datos se almacenan en Postgres. Por tanto, los contenedores se pueden descartar: para actualizar, sustitúyalos; para revertir la actualización, fije la etiqueta anterior y restaure el volcado.
Modos de fallo y los mensajes que verá
The requested webhook "POST hello" is not registered. Un 404 al invocar un webhook cuyo flujo de trabajo no está publicado, o al invocar la ruta de prueba cuando no hay ningún proceso 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 se publica el flujo de trabajo. El mensaje relacionado 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 :5678 o localhost. El nodo muestra https://n8n.example.com:5678/webhook/... o http://localhost:5678/.... WEBHOOK_URL no está definida o es incorrecta, por lo que n8n construyó la dirección a partir de N8N_HOST:N8N_PORT en lugar de usar su 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 valor incorrecto de N8N_HOST o WEBHOOK_URL, a que el proxy no reenvía las cabeceras Upgrade de WebSocket o a que N8N_PROTOCOL no coincide con la forma 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 registros, mientras el contenedor se reinicia. La contraseña que n8n envía no coincide con la que se utilizó para inicializar la base de datos. La particularidad es que Postgres lee POSTGRES_PASSWORD sólo cuando inicializa un directorio de datos vacío. Inicie la pila una vez, cambie después POSTGRES_PASSWORD en .env y el volumen postgres_data existente seguirá conservando la contraseña anterior. Restablezca la contraseña original o, si no necesita conservar los datos, docker compose down y docker volume rm el volumen de postgres y vuelva a iniciarlo 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 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 necesita usar un montaje de enlace, 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 de forma predeterminada a ese archivo de configuración y lo corrige durante el arranque. Este mensaje indica que ya corrigió los permisos, normalmente después de un montaje de enlace o de restaurar una copia que volvió a poner 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 realmente no admite permisos.
Mismatching encryption keys; el mensaje completo 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 es distinta 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 estableció otra. Vuelva a poner la clave original en .env o, sólo si realmente no necesita conservar ninguna credencial almacenada, elimine el archivo config dentro del volumen n8n_data y deje 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.. Ha definido N8N_PROTOCOL=https, pero accedió a n8n mediante HTTP sin cifrar, normalmente al conectarse directamente a la IP y al puerto en lugar de usar el 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 cada vez. Use Postgres para cualquier entorno del que dependa: el bloqueo de escritura único de SQLite provoca database is locked con concurrencia, y Postgres permite realizar copias de seguridad correctamente con pg_dump. La migración posterior es manual, así 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á definida o es incorrecta, n8n muestra direcciones de webhook generadas 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 solicitudes del cliente nunca llegan. Defina WEBHOOK_URL=https://n8n.example.com/ y confirme que el nodo muestra una URL sin puerto. La segunda causa es llamar a la URL de producción de un flujo que no ha publicado, lo que devuelve The requested webhook ... is not registered.
¿Qué debo incluir en las copias de seguridad de n8n?
Dos cosas. El N8N_ENCRYPTION_KEY del archivo .env, porque todas las credenciales almacenadas se cifran con él y, si se pierde, dejan de poder descifrarse permanentemente. Cópielo fuera del servidor el mismo día que lo cree. También necesita un pg_dump de la base de datos de Postgres con los flujos, 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 termina 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ía las cabeceras Upgrade de WebSocket. De lo contrario, el editor se queda bloqueado.
¿Cómo actualizo n8n de forma segura?
Fije una etiqueta de imagen específica en lugar de latest. Cree primero un pg_dump, porque n8n ejecuta migraciones automáticamente al iniciarse. Lea las notas de la versión para comprobar si hay cambios incompatibles. Después, actualice la etiqueta y ejecute docker compose pull n8n && docker compose up -d n8n. El contenedor es desechable, así que puede revertir el cambio fijando la etiqueta anterior y restaurando el volcado creado antes de la actualización.