Cómo autoalojar n8n en un VPS NVMe con Docker y SSL paso a paso
Una instalación de producción reproducible: Docker Compose con PostgreSQL, volúmenes persistentes, HTTPS automático con Caddy (o Nginx) y el plan de copias y actualizaciones que la mantiene viva.
En esta página
Qué vamos a montar y por qué así
Este tutorial deja n8n funcionando en un servidor privado virtual (VPS) con una configuración pensada para producción, no para una prueba de diez minutos. La instalación tiene tres contenedores gestionados con Docker Compose:
- n8n, el motor de automatización, sin ningún puerto publicado hacia internet.
- PostgreSQL, la base de datos de flujos, credenciales cifradas e historial de ejecuciones.
- Caddy, el proxy inverso que recibe el tráfico en los puertos 80 y 443, obtiene y renueva el certificado HTTPS de Let's Encrypt y reenvía las peticiones a n8n por la red interna de Docker.
La guía oficial de n8n con Docker Compose usa Traefik como proxy y la base de datos SQLite integrada. Aquí cambiamos dos piezas a propósito: Caddy, porque su configuración cabe en tres líneas y gestiona los certificados sin etiquetas ni ficheros adicionales, y PostgreSQL, porque separa los datos del contenedor de la aplicación y permite copias consistentes con pg_dump. Si prefieres Nginx, al final del apartado del proxy tienes la configuración equivalente.
Antes de empezar: la propia documentación de n8n advierte de que el autoalojamiento está pensado para personas con experiencia en administración de sistemas. Si nadie de tu equipo va a mantener el servidor, un servicio alojado suele salir más barato en conjunto.
Requisitos previos
- Un VPS con Ubuntu 24.04 LTS recién instalado y acceso por SSH con clave. Como punto de partida razonable para un uso individual o de pyme, 2 vCPU, 4 GB de memoria y disco NVMe; es un criterio editorial, no una cifra oficial: la guía de Docker Compose de n8n no fija requisitos mínimos de hardware. Ajusta con datos de uso reales.
- Un dominio y acceso a su zona DNS para crear un registro
A(yAAAAsi tu servidor tiene IPv6) del tipon8n.tudominio.comapuntando a la IP del servidor. - Los puertos 80 y 443 abiertos en el cortafuegos del proveedor, si lo tiene. Let's Encrypt necesita llegar al puerto 80 o al 443 para validar el dominio.
- Un correo de contacto para el registro de los certificados.
Por qué insistimos en NVMe: n8n escribe en la base de datos en cada ejecución (estado, datos de entrada y salida de cada nodo). Con muchos flujos cortos, el cuello de botella suele ser la latencia de escritura sincronizada del disco, no la CPU. En la comparativa de proveedores explicamos cómo medirla con fio antes de migrar.
Paso 1. Preparar el servidor
Conéctate como root (o con el usuario inicial que te dé el proveedor), actualiza el sistema y crea un usuario de administración que no sea root:
apt update && apt full-upgrade -y
adduser admin
usermod -aG sudo admin
rsync --archive --chown=admin:admin ~/.ssh /home/admin
Comprueba desde otra terminal que puedes entrar como admin con tu clave. Después desactiva el acceso por contraseña y el acceso directo de root editando /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
sudo systemctl restart ssh
Activa el cortafuegos dejando pasar solo SSH, HTTP y HTTPS, y las actualizaciones de seguridad automáticas:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Docker y ufw: los puertos que publica Docker con ports: se abren mediante reglas propias de iptables y no pasan por ufw. Por eso, en este montaje solo Caddy publica puertos (80 y 443); n8n y PostgreSQL no publican ninguno y solo son accesibles dentro de la red interna de Docker.
Paso 2. Instalar Docker Engine y Compose
Instala Docker desde su repositorio oficial, no desde el paquete docker.io de la distribución, para recibir el complemento docker compose actualizado. Son los pasos de la documentación de Docker para Ubuntu:
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Comprueba la instalación:
docker --version
docker compose version
Puedes añadir tu usuario al grupo docker para no escribir sudo en cada orden (sudo usermod -aG docker admin y volver a iniciar sesión), sabiendo que ese grupo equivale en la práctica a permisos de administrador del servidor.
Paso 3. El proyecto con Docker Compose
Crea una carpeta para el proyecto. Todo lo que define la instalación vivirá en ella, y los datos, en volúmenes de Docker:
mkdir -p ~/n8n && cd ~/n8n
touch .env compose.yaml Caddyfile
chmod 600 .env
Las variables: el fichero .env
Genera dos secretos aleatorios, uno para la contraseña de PostgreSQL y otro para la clave de cifrado de n8n, con la que se cifran las credenciales guardadas:
openssl rand -hex 24 # contraseña de PostgreSQL
openssl rand -hex 32 # N8N_ENCRYPTION_KEY
Y rellena .env con tus valores:
# Dominio público de n8n
N8N_DOMINIO=n8n.tudominio.com
CORREO_SSL=tu-correo@tudominio.com
ZONA_HORARIA=Europe/Madrid
# Versión fijada de n8n: cámbiala tú, con copia previa (ver «Actualizaciones»)
N8N_VERSION=2.43.7
# PostgreSQL
POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=pega-aqui-la-primera-clave
# Cifrado de credenciales de n8n: NO la pierdas ni la cambies después
N8N_ENCRYPTION_KEY=pega-aqui-la-segunda-clave
La versión 2.43.7 era la estable que indicaba la documentación de n8n al escribir este tutorial. Fijar la versión, en lugar de usar latest, evita que un reinicio cualquiera te actualice sin haber hecho copia ni leído las notas de la versión.
Los servicios: compose.yaml
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
DB_POSTGRESDB_USER: ${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_DOMINIO}
N8N_PORT: 5678
N8N_PROTOCOL: https
N8N_WEBHOOK_URL: https://${N8N_DOMINIO}/
N8N_PROXY_HOPS: 1
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
NODE_ENV: production
GENERIC_TIMEZONE: ${ZONA_HORARIA}
TZ: ${ZONA_HORARIA}
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: 336
volumes:
- n8n_data:/home/node/.n8n
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
N8N_DOMINIO: ${N8N_DOMINIO}
CORREO_SSL: ${CORREO_SSL}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
postgres_data:
n8n_data:
caddy_data:
caddy_config:
Lo que conviene entender de este fichero:
- n8n no tiene
ports:. Caddy llega a él por el nombre del servicio (n8n:5678) dentro de la red que Compose crea para el proyecto. Nadie puede saltarse el proxy ni el HTTPS. N8N_WEBHOOK_URLes la dirección pública con la que n8n construye las URL de los webhooks. Sin ella, el editor mostraría direcciones conlocalhosto con el puerto interno. Según la documentación de n8n, esta variable se aplica desde la versión 2.30.0; en versiones anteriores se llamabaWEBHOOK_URL.N8N_PROXY_HOPS: 1indica que hay un proxy delante, para que n8n confíe en la cabeceraX-Forwarded-Fory registre la IP real del cliente.EXECUTIONS_DATA_PRUNEyEXECUTIONS_DATA_MAX_AGEborran el historial de ejecuciones con más de 336 horas (14 días). Sin poda, la base de datos crece sin límite y el disco acaba lleno.depends_onconservice_healthyhace que n8n espere a que PostgreSQL acepte conexiones, en lugar de fallar y reiniciarse en bucle durante el arranque.
Si todavía no has decidido qué vas a automatizar, merece la pena hacerlo antes de seguir: en la guía de automatización con IA y flujos de trabajo explicamos cómo elegir el primer flujo y describirlo en una ficha del proceso, y esa ficha te dirá también cuántas ejecuciones al mes debe soportar el servidor.
Paso 4. Volúmenes persistentes: qué se guarda y dónde
Un contenedor es desechable: cada actualización lo destruye y crea uno nuevo a partir de la imagen. Todo lo que debe sobrevivir tiene que vivir en un volumen. En este montaje hay cuatro, todos con nombre (Docker los guarda en /var/lib/docker/volumes/):
| Volumen | Ruta en el contenedor | Contenido | Si se pierde |
|---|---|---|---|
| postgres_data | /var/lib/postgresql/data | Flujos, credenciales cifradas, usuarios e historial de ejecuciones. | Pierdes todo el trabajo. Es lo primero que hay que copiar. |
| n8n_data | /home/node/.n8n | Configuración local de n8n, incluido el fichero donde guarda la clave de cifrado si no la defines por variable. | Con la clave en .env, se regenera; sin ella, las credenciales quedan ilegibles. |
| caddy_data | /data | Certificados TLS y la cuenta ACME. | Caddy los vuelve a pedir; si ocurre a menudo, puedes chocar con los límites de emisión de Let's Encrypt. |
| caddy_config | /config | Configuración autoguardada de Caddy. | Se regenera sin consecuencias. |
La pieza más delicada no está en ningún volumen: es la N8N_ENCRYPTION_KEY de tu .env. Si restauras la base de datos en otro servidor con una clave distinta, n8n arrancará, pero no podrá descifrar ninguna credencial. Guarda una copia del .env en un gestor de contraseñas, fuera del servidor.
Usamos volúmenes con nombre en lugar de carpetas del servidor (bind mounts) porque Docker gestiona sus permisos: la imagen de n8n se ejecuta con el usuario node (UID 1000) y una carpeta del sistema con otro propietario provoca errores de permisos al arrancar.
Paso 5. Proxy inverso y SSL
Opción A: Caddy (la que usa este tutorial)
El Caddyfile completo es este:
{
email {$CORREO_SSL}
}
{$N8N_DOMINIO} {
encode zstd gzip
reverse_proxy n8n:5678
}
Con un nombre de dominio como dirección del sitio, Caddy activa el HTTPS automático: obtiene el certificado de Let's Encrypt, redirige HTTP a HTTPS y renueva el certificado antes de que caduque, sin tareas programadas. reverse_proxy reenvía también las conexiones WebSocket que usa el editor de n8n para mostrar en directo el avance de las ejecuciones, y añade por defecto las cabeceras X-Forwarded-For y X-Forwarded-Proto.
Opción B: Nginx con Certbot en el servidor
Si ya tienes Nginx en el servidor, quita el servicio caddy del compose.yaml, publica n8n solo en la interfaz local y deja que Nginx haga de proxy:
n8n:
# ...
ports:
- "127.0.0.1:5678:5678"
Crea /etc/nginx/sites-available/n8n:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name n8n.tudominio.com;
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_read_timeout 3600s;
client_max_body_size 50m;
}
}
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d n8n.tudominio.com --redirect
Las cabeceras Upgrade y Connection son las que suelen faltar: sin ellas, el editor muestra avisos de conexión perdida y no actualiza las ejecuciones en directo. proxy_buffering off evita que Nginx retenga las respuestas en flujo, y client_max_body_size permite webhooks con ficheros adjuntos de hasta 50 MB (ajústalo a tu caso). Certbot instala una tarea de renovación automática; compruébala con sudo certbot renew --dry-run.
Paso 6. Arrancar y comprobar
cd ~/n8n
docker compose up -d
docker compose ps
docker compose logs -f n8n
Los tres servicios deben aparecer como running y PostgreSQL como healthy. En los registros de n8n verás las migraciones de la base de datos en el primer arranque. Desde tu ordenador, comprueba el certificado y la redirección:
curl -I http://n8n.tudominio.com # debe responder con una redirección a https
curl -I https://n8n.tudominio.com # debe responder 200 sin errores de certificado
Crea la cuenta de propietario en cuanto arranque. La primera persona que abre la instancia configura la cuenta de propietario. No dejes el servidor accesible sin haberlo hecho, y activa después la verificación en dos pasos en la configuración de tu usuario.
Por último, crea un flujo de prueba con un nodo Webhook y comprueba que la URL de producción que muestra empieza por https://n8n.tudominio.com/webhook/. Si ves localhost o :5678, revisa N8N_WEBHOOK_URL.
¿Dudas todavía entre mantener tu propia instalación o pagar un servicio alojado? En la comparativa de n8n, Make y Zapier tienes los precios comprobados de n8n Cloud y la forma de contar el consumo de cada plataforma, para comparar con la cuota del VPS más tu tiempo de administración.
Paso 7. Copias de seguridad y restauración
Una instalación sin copias probadas no es una instalación de producción. Este script vuelca la base de datos y la configuración en un archivo con fecha:
#!/usr/bin/env bash
# ~/n8n/copia.sh — copia diaria de n8n
set -euo pipefail
cd ~/n8n
DESTINO=~/copias
FECHA=$(date +%Y-%m-%d_%H%M)
mkdir -p "$DESTINO"
docker compose exec -T postgres pg_dump -U n8n -d n8n --format=custom > "$DESTINO/n8n_$FECHA.dump"
cp .env compose.yaml Caddyfile "$DESTINO/"
find "$DESTINO" -name 'n8n_*.dump' -mtime +14 -delete
chmod +x ~/n8n/copia.sh
crontab -e
# Añade la línea: copia diaria a las 03:15
15 3 * * * /home/admin/n8n/copia.sh
Tres reglas para que la copia sirva de algo:
- Sácala del servidor. Una copia en el mismo disco no te protege de la pérdida del VPS. Sincroniza
~/copiascon un almacenamiento externo (por ejemplo, un almacenamiento de objetos compatible con S3 medianterclone), cifrada si contiene datos personales. - Complementa con las instantáneas del proveedor, pero no las uses como única copia: una instantánea del disco tomada en mitad de una escritura de PostgreSQL no garantiza una base de datos consistente;
pg_dump, sí. - Restaura una vez al mes en un servidor de pruebas. Es la única forma de saber que la copia funciona.
Para restaurar en una instalación nueva con el mismo .env (y por tanto la misma clave de cifrado):
docker compose up -d postgres
docker compose exec -T postgres pg_restore -U n8n -d n8n --clean --if-exists < n8n_AAAA-MM-DD_HHMM.dump
docker compose up -d
Como copia adicional y legible, la interfaz de línea de órdenes de n8n exporta los flujos a JSON: docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/flujos.json. Útil para el control de versiones, pero no sustituye al volcado de la base de datos.
Paso 8. Actualizaciones sin sustos
Con la versión fijada en .env, actualizar es un cambio deliberado:
- Lee las notas de la versión de n8n, sobre todo si cambia el número principal.
- Ejecuta
~/n8n/copia.sh. - Cambia
N8N_VERSIONen.env. - Descarga e instala:
docker compose pull n8n && docker compose up -d n8n. - Revisa
docker compose logs -f n8nhasta que terminen las migraciones y prueba tus flujos críticos.
Si algo falla, vuelve a la versión anterior en .env y restaura la copia: algunas migraciones de base de datos no son reversibles, por lo que volver atrás solo con la imagen no siempre basta. Para PostgreSQL, el cambio de versión principal (por ejemplo, de 16 a 17) exige volcar y restaurar los datos; no cambies la etiqueta de la imagen sin hacerlo.
Limpia de vez en cuando las imágenes antiguas para no llenar el disco: docker image prune.
Endurecimiento y supervisión
- Superficie mínima: solo 22, 80 y 443 abiertos; n8n y PostgreSQL sin puertos publicados.
- Acceso: SSH solo con clave; verificación en dos pasos para todas las cuentas de n8n.
- Supervisión externa: configura un monitor de disponibilidad desde fuera del servidor que compruebe la URL de n8n cada pocos minutos y te avise si cae. Un monitor dentro del mismo VPS no te avisará si el servidor entero deja de responder.
- Disco: vigila el espacio libre (
df -h) y el tamaño de los volúmenes (docker system df -v). La poda de ejecuciones evita la causa más habitual de disco lleno. - Errores de flujo: crea un flujo de errores en n8n (nodo Error Trigger) que te notifique por correo o mensajería cuando falle una ejecución.
- Datos personales: si tus flujos tratan datos de clientes, elige un centro de datos en la Unión Europea y revisa el contrato de encargado del tratamiento de tu proveedor de VPS.
Problemas frecuentes y su causa
| Síntoma | Causa probable | Solución |
|---|---|---|
| Caddy no obtiene el certificado | El registro DNS aún no apunta al servidor o el puerto 80/443 está cerrado en el cortafuegos del proveedor. | Comprueba con dig +short n8n.tudominio.com y revisa docker compose logs caddy. |
| Las URL de webhook muestran localhost | Falta N8N_WEBHOOK_URL (o WEBHOOK_URL antes de la 2.30.0). | Defínela con la URL pública terminada en barra y recrea el contenedor. |
| El editor avisa de conexión perdida | El proxy no reenvía WebSocket. | En Nginx, añade las cabeceras Upgrade y Connection. |
| n8n se reinicia en bucle al arrancar | Credenciales de PostgreSQL incorrectas o base de datos aún no lista. | Revisa .env y el healthcheck; mira docker compose logs n8n. |
| Credenciales que no se pueden descifrar | La N8N_ENCRYPTION_KEY no es la original. | Restaura el .env original. Sin la clave, hay que volver a introducir las credenciales. |
| Disco lleno | Historial de ejecuciones sin podar, copias locales o imágenes antiguas. | Activa la poda, saca las copias del servidor y ejecuta docker image prune. |
Preguntas frecuentes
¿Puedo usar SQLite en lugar de PostgreSQL?
Sí: es la opción por defecto de n8n y la que usa su guía de Docker Compose. Para un uso pequeño funciona; elegimos PostgreSQL porque facilita copias consistentes con pg_dump y separa los datos de la aplicación.
¿Caddy o Nginx?
Si el servidor solo va a alojar n8n, Caddy es más sencillo: HTTPS y renovación automáticos con tres líneas de configuración. Si ya administras Nginx con otros sitios, usa la opción B para no tener dos proxies compitiendo por los puertos 80 y 443.
¿Cuánta memoria necesito?
La guía de Docker Compose de n8n no fija un mínimo. Empieza con 4 GB si vas a usar PostgreSQL en el mismo servidor y vigila el uso real con docker stats durante las primeras semanas.
¿Necesito una licencia para la edición Community?
No. Sin clave de licencia, la instalación funciona como edición Community, sin cuota. Algunas funciones avanzadas requieren los planes de pago de n8n.
Fuentes y metodología
Este tutorial se basa en la documentación oficial enlazada a continuación, consultada el 16 de octubre de 2026. Las órdenes son las de esas guías, adaptadas a un montaje con PostgreSQL y Caddy; las cifras de dimensionado son criterio editorial, no mediciones de NexoFlujos.
- n8n: instalación con Docker Compose y documentación de alojamiento
- Docker: instalar Docker Engine en Ubuntu
- Caddy: HTTPS automático y directiva reverse_proxy
- Imagen oficial de PostgreSQL
Para elegir el servidor, consulta nuestra comparativa de proveedores de hosting VPS y Cloud. La política editorial completa está en Sobre Larbi Nokra y la metodología de NexoFlujos.