Saltar al contenido
NexoFlujos
Tutorial de infraestructura

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.

NexoFlujosResponsable editorial: Larbi NokraActualizado: Lectura: 17 min

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 (y AAAA si tu servidor tiene IPv6) del tipo n8n.tudominio.com apuntando 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_URL es la dirección pública con la que n8n construye las URL de los webhooks. Sin ella, el editor mostraría direcciones con localhost o 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 llamaba WEBHOOK_URL.
  • N8N_PROXY_HOPS: 1 indica que hay un proxy delante, para que n8n confíe en la cabecera X-Forwarded-For y registre la IP real del cliente.
  • EXECUTIONS_DATA_PRUNE y EXECUTIONS_DATA_MAX_AGE borran 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_on con service_healthy hace 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/):

Volúmenes de la instalación y qué se pierde si desaparecen
VolumenRuta en el contenedorContenidoSi se pierde
postgres_data/var/lib/postgresql/dataFlujos, credenciales cifradas, usuarios e historial de ejecuciones.Pierdes todo el trabajo. Es lo primero que hay que copiar.
n8n_data/home/node/.n8nConfiguració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/dataCertificados 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/configConfiguració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 ~/copias con un almacenamiento externo (por ejemplo, un almacenamiento de objetos compatible con S3 mediante rclone), 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:

  1. Lee las notas de la versión de n8n, sobre todo si cambia el número principal.
  2. Ejecuta ~/n8n/copia.sh.
  3. Cambia N8N_VERSION en .env.
  4. Descarga e instala: docker compose pull n8n && docker compose up -d n8n.
  5. Revisa docker compose logs -f n8n hasta 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íntomas habituales tras la instalación y dónde mirar
SíntomaCausa probableSolución
Caddy no obtiene el certificadoEl 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 localhostFalta 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 perdidaEl proxy no reenvía WebSocket.En Nginx, añade las cabeceras Upgrade y Connection.
n8n se reinicia en bucle al arrancarCredenciales 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 descifrarLa N8N_ENCRYPTION_KEY no es la original.Restaura el .env original. Sin la clave, hay que volver a introducir las credenciales.
Disco llenoHistorial 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.

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.