n8n es una plataforma de automatización de flujos de trabajo con editor visual y cientos de integraciones (Slack, Gmail, Google Sheets, bases de datos, APIs HTTP), que puedes alojar en tu propio servidor como alternativa a Zapier o Make. En este tutorial desplegarás n8n con Docker Compose y PostgreSQL en Ubuntu 24.04, lo publicarás detrás de Nginx con un certificado de Let's Encrypt, crearás un flujo que responde a un webhook y programarás copias de seguridad diarias.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM.
  • Un usuario no root con privilegios sudo.
  • Docker Engine y el plugin Docker Compose instalados desde el repositorio oficial de Docker.
  • Un subdominio (en esta guía, n8n.your_domain) con un registro DNS A que apunte a la IP pública del servidor.
  • Los puertos 80 y 443 accesibles desde Internet.

Comprueba que Docker y Compose responden:

docker --version
docker compose version

Paso 1: Crear el directorio y los secretos

Crea el directorio del proyecto:

sudo mkdir -p /opt/n8n
cd /opt/n8n

n8n necesita dos secretos: la contraseña de PostgreSQL y una clave de cifrado (N8N_ENCRYPTION_KEY) con la que cifra las credenciales que guardes en los flujos. Si pierdes esa clave, las credenciales almacenadas dejan de poder descifrarse, así que guárdala también fuera del servidor.

Crea el archivo .env con valores aleatorios. Docker Compose lo lee automáticamente desde el directorio del proyecto:

sudo tee /opt/n8n/.env > /dev/null <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
N8N_DOMAIN=n8n.your_domain
TIMEZONE=Europe/Madrid
EOF
sudo chmod 600 /opt/n8n/.env

Sustituye n8n.your_domain por tu subdominio y ajusta TIMEZONE a tu zona horaria. La zona horaria afecta a los nodos programados (Schedule Trigger).

Paso 2: Crear el archivo de Docker Compose

Crea el archivo de Compose:

sudo nano /opt/n8n/compose.yaml

Añade el siguiente contenido:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_HOST: ${N8N_DOMAIN}
      N8N_PORT: 5678
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_DOMAIN}/
      N8N_PROXY_HOPS: 1
      GENERIC_TIMEZONE: ${TIMEZONE}
      TZ: ${TIMEZONE}
      NODE_ENV: production
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

Qué hace cada ajuste relevante:

  • DB_TYPE: postgresdb sustituye la base de datos SQLite por defecto por PostgreSQL, más adecuada para producción.
  • N8N_PROTOCOL, N8N_HOST y WEBHOOK_URL indican a n8n su URL pública. Sin WEBHOOK_URL, el editor mostraría las URL de los webhooks con http://localhost:5678.
  • N8N_PROXY_HOPS: 1 le dice a n8n que hay un proxy inverso delante, para que lea correctamente la IP del cliente de X-Forwarded-For.
  • El puerto se publica solo en 127.0.0.1. Docker gestiona sus propias reglas de iptables y un puerto publicado en todas las interfaces quedaría accesible aunque UFW lo bloquee.

Paso 3: Arrancar n8n

Arranca los contenedores:

cd /opt/n8n
sudo docker compose up -d

Comprueba su estado:

sudo docker compose ps
NAME             IMAGE                            SERVICE    STATUS                    PORTS
n8n-n8n-1        docker.n8n.io/n8nio/n8n:latest   n8n        Up 30 seconds             127.0.0.1:5678->5678/tcp
n8n-postgres-1   postgres:16                      postgres   Up 41 seconds (healthy)   5432/tcp

n8n expone el endpoint /healthz. Consúltalo desde el servidor:

curl -s http://127.0.0.1:5678/healthz
{"status":"ok"}

Si no responde, revisa los registros con sudo docker compose logs n8n. Un error de conexión a la base de datos suele indicar que el archivo .env no está en /opt/n8n o que cambiaste la contraseña después de crear el volumen de PostgreSQL.

Paso 4: Configurar Nginx y HTTPS

Instala Nginx y Certbot:

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Crea el bloque de servidor:

sudo nano /etc/nginx/sites-available/n8n

Añade esta configuración. El editor de n8n recibe actualizaciones en tiempo real por WebSocket, por eso se reenvían las cabeceras Upgrade y Connection; proxy_buffering off evita retrasos en esas respuestas y el proxy_read_timeout alto da margen a los webhooks que tardan en responder:

server {
    listen 80;
    listen [::]:80;
    server_name n8n.your_domain;

    client_max_body_size 50M;

    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-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 300s;
    }
}

Activa el sitio y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Si usas UFW, permite SSH y el tráfico web:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Obtén el certificado. Certbot añade la configuración TLS y la redirección a HTTPS:

sudo certbot --nginx -d n8n.your_domain

Comprueba que la renovación automática funcionará:

sudo certbot renew --dry-run

Paso 5: Crear la cuenta de propietario

Abre https://n8n.your_domain. En el primer acceso, n8n pide crear la cuenta de propietario (owner): introduce tu correo, nombre y una contraseña robusta. Esta cuenta administra la instancia; a otros usuarios los invitarás después desde Settings y luego Users.

Paso 6: Crear un flujo con webhook

Para comprobar que la instalación funciona de extremo a extremo, crea un flujo que recibe una petición HTTP y devuelve una respuesta:

  1. Pulsa Create Workflow (o Add workflow) y ponle el nombre Prueba webhook.
  2. Añade el nodo disparador Webhook. En HTTP Method elige POST y en Path escribe prueba.
  3. Guarda el flujo.
  4. Pulsa Listen for test event en el nodo Webhook. n8n mostrará la URL de prueba, que contiene /webhook-test/prueba.

Desde tu equipo, envía una petición a esa URL:

curl -X POST https://n8n.your_domain/webhook-test/prueba \
  -H 'Content-Type: application/json' \
  -d '{"nombre": "CubePath"}'
{"message":"Workflow was started"}

En el editor verás los datos recibidos en la salida del nodo Webhook. Las URL de prueba solo funcionan mientras el editor está escuchando. Para que el flujo atienda peticiones de forma permanente, actívalo (o publícalo, según tu versión de n8n) con el control de la parte superior del editor. A partir de ahí responde en la URL de producción:

curl -X POST https://n8n.your_domain/webhook/prueba \
  -H 'Content-Type: application/json' \
  -d '{"nombre": "CubePath"}'

Cada ejecución queda registrada en la pestaña Executions del flujo. Si la URL que muestra el nodo empieza por http://localhost:5678, revisa la variable WEBHOOK_URL del paso 2.

Las credenciales de servicios externos (tokens de Slack, cuentas de Google, claves de API) se crean desde Credentials o directamente desde el nodo que las necesita, y se guardan cifradas con N8N_ENCRYPTION_KEY.

Paso 7: Programar copias de seguridad

Una copia completa de n8n consta del volcado de PostgreSQL (flujos, credenciales cifradas, historial de ejecuciones) y del archivo .env, que contiene la clave de cifrado. Crea el script:

sudo nano /usr/local/bin/n8n-backup

Añade el siguiente contenido:

#!/usr/bin/env bash
set -euo pipefail

PROJECT_DIR="/opt/n8n"
BACKUP_DIR="/var/backups/n8n"
STAMP="$(date +%Y%m%d-%H%M%S)"

mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
cd "$PROJECT_DIR"

docker compose exec -T postgres pg_dump -U n8n -d n8n \
    | gzip > "$BACKUP_DIR/n8n-db-$STAMP.sql.gz"
cp .env "$BACKUP_DIR/n8n-env-$STAMP"
chmod 600 "$BACKUP_DIR/n8n-env-$STAMP"

# Conserva las copias de los últimos 14 días
find "$BACKUP_DIR" -type f -name 'n8n-*' -mtime +14 -delete

Hazlo ejecutable, ejecútalo una vez y comprueba el resultado:

sudo chmod 750 /usr/local/bin/n8n-backup
sudo /usr/local/bin/n8n-backup
sudo ls -lh /var/backups/n8n
-rw------- 1 root root  71 sep 25 03:30 n8n-env-20260925-033000
-rw-r--r-- 1 root root 48K sep 25 03:30 n8n-db-20260925-033000.sql.gz

Prográmalo cada noche a las 03:30:

echo '30 3 * * * root /usr/local/bin/n8n-backup' | sudo tee /etc/cron.d/n8n-backup

Copia regularmente /var/backups/n8n a otro servidor o a almacenamiento de objetos: una copia en el mismo disco no te protege de un fallo del servidor.

Paso 8: Actualizar n8n

n8n publica versiones cada semana. Haz una copia de seguridad, descarga la imagen nueva y recrea el contenedor:

sudo /usr/local/bin/n8n-backup
cd /opt/n8n
sudo docker compose pull
sudo docker compose up -d

Las migraciones de la base de datos se ejecutan al arrancar. Revisa las notas de versión antes de saltar a una versión mayor, porque pueden incluir cambios incompatibles en nodos o variables de entorno. Si prefieres controlar cuándo cambias de versión, sustituye latest por una versión concreta en compose.yaml.

Solución de problemas

  • 502 Bad Gateway: n8n no escucha en 127.0.0.1:5678. Comprueba sudo docker compose ps y curl http://127.0.0.1:5678/healthz.
  • El editor muestra "Connection lost": el WebSocket no pasa por Nginx. Revisa las cabeceras Upgrade y Connection del bloque de servidor.
  • Mismatching encryption keys en los registros: la clave de .env no coincide con la que se usó al crear los datos. Restaura el .env original de la copia de seguridad.
  • Los nodos programados se ejecutan a la hora equivocada: revisa TIMEZONE en .env y la zona horaria del propio flujo en sus ajustes.

Conclusión

Ya tienes n8n funcionando con PostgreSQL, detrás de Nginx con HTTPS, con un flujo de webhook probado y copias de seguridad diarias. Como siguientes pasos, conecta tus primeras credenciales para automatizar tareas reales, invita a tu equipo desde Settings y, si el volumen de ejecuciones crece, configura la poda del historial con las variables EXECUTIONS_DATA_PRUNE y EXECUTIONS_DATA_MAX_AGE.