Desplegando n8n en Docker con Nginx y SSL en producción
Cómo montar un servidor de automatización privado, seguro y escalable en cualquier VPS por menos de 5€/mes. Sin nubes de pago, sin vendor lock-in, con backups y configuración real de producción.
Por qué self-hosted y no n8n Cloud
n8n ofrece una versión Cloud gestionada con planes desde 20€/mes con ejecuciones limitadas. Para casos de uso simples está bien — pero en cuanto el volumen crece o los datos no pueden salir del perímetro, self-hosted es la única opción viable.
n8n Cloud
20€
/ mes — plan Starter
2.500 ejecuciones/mes
5 workflows activos
Datos en cloud de terceros
Sin acceso al backend
Self-hosted (VPS)
4€
/ mes — Hetzner CX22
Ejecuciones ilimitadas
Workflows ilimitados
Datos en tu infraestructura
Control total + plugins
Para entornos críticos donde los datos no pueden salir del perímetro — banca, salud, telecom, administración pública — self-hosted no es opción: es obligatorio. Pero incluso para casos no regulados, la diferencia de coste y flexibilidad hace que la decisión sea evidente cuando el volumen escala.
Arquitectura objetivo
El stack que vamos a desplegar:
- n8n en contenedor Docker, conectado a PostgreSQL externo (no SQLite — explico por qué más abajo)
- PostgreSQL 15 en otro contenedor, con volumen persistente
- Nginx en el host como reverse proxy, terminando SSL y enrutando hacia n8n
- Let's Encrypt con renovación automática
- Backups automáticos de la base de datos a almacenamiento externo
Paso 1: Provisión del VPS
Recomendaciones por proveedor (mayo 2026):
- Hetzner CX22: 2 vCPU, 4GB RAM, 40GB SSD — 4,15€/mes. La mejor relación precio/calidad para este caso de uso.
- DigitalOcean Basic: 1 vCPU, 1GB RAM — 6$/mes. Suficiente para empezar pero se queda corto rápido si el workflow es pesado.
- OVH VPS Starter: opción europea con buena conectividad si tienes integraciones con datacenters franceses o españoles.
Sistema operativo recomendado: Debian 12 o Ubuntu 24.04 LTS. Ambos están bien soportados, con paquetes recientes y ciclos de vida largos.
Paso 2: Hardening básico antes de nada
Antes de instalar nada, asegura el servidor. Estos pasos son la diferencia entre tener un VPS y tener un VPS comprometido:
# Crear usuario no-root adduser deploy usermod -aG sudo deploy usermod -aG docker deploy # Configurar SSH solo con clave (deshabilitar password) mkdir -p /home/deploy/.ssh cp ~/.ssh/authorized_keys /home/deploy/.ssh/ chown -R deploy:deploy /home/deploy/.ssh # /etc/ssh/sshd_config PasswordAuthentication no PermitRootLogin no PubkeyAuthentication yes systemctl restart sshd # Firewall: solo SSH y HTTP/HTTPS ufw default deny incoming ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable # fail2ban contra fuerza bruta SSH apt install fail2ban -y systemctl enable --now fail2ban
Paso 3: Instalar Docker
# Script oficial de Docker curl -fsSL https://get.docker.com | sudo sh # Verificar instalación docker --version docker compose version # Test rápido docker run hello-world
Paso 4: docker-compose.yml de producción
Crea un directorio ~/n8n y dentro coloca este archivo. Es la configuración real que usamos en producción, sin secretos hardcodeados:
# ~/n8n/docker-compose.yml services: postgres: image: postgres:15-alpine restart: always environment: POSTGRES_DB: n8n POSTGRES_USER: n8n POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data networks: - internal healthcheck: test: ["CMD-SHELL", "pg_isready -U n8n"] interval: 10s timeout: 5s retries: 5 n8n: image: n8nio/n8n:latest restart: always ports: - "127.0.0.1:5678:5678" # solo localhost — Nginx hace de proxy environment: - N8N_HOST=${N8N_HOST} - N8N_PORT=5678 - N8N_PROTOCOL=https - WEBHOOK_URL=https://${N8N_HOST}/ - N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY} - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_DATABASE=n8n - DB_POSTGRESDB_USER=n8n - DB_POSTGRESDB_PASSWORD=${DB_PASSWORD} - N8N_LOG_LEVEL=info - EXECUTIONS_DATA_PRUNE=true - EXECUTIONS_DATA_MAX_AGE=336 # 14 días volumes: - n8n_data:/home/node/.n8n depends_on: postgres: condition: service_healthy networks: - internal volumes: pg_data: n8n_data: networks: internal: driver: bridge
Y el archivo .env en el mismo directorio:
# ~/n8n/.env N8N_HOST=n8n.tudominio.com DB_PASSWORD=$(openssl rand -base64 32) ENCRYPTION_KEY=$(openssl rand -base64 32)
Paso 5: Nginx como reverse proxy
Instala Nginx en el host (no en Docker — es más fácil gestionar SSL así):
sudo apt install nginx certbot python3-certbot-nginx -y
Configuración del sitio en /etc/nginx/sites-available/n8n:
# /etc/nginx/sites-available/n8n server { listen 80; server_name n8n.tudominio.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name n8n.tudominio.com; # Certificados — los crea certbot en el siguiente paso ssl_certificate /etc/letsencrypt/live/n8n.tudominio.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/n8n.tudominio.com/privkey.pem; # Hardening SSL ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; add_header Strict-Transport-Security "max-age=31536000"; # Tamaño máximo de petición — útil para workflows que reciben archivos client_max_body_size 50M; location / { proxy_pass http://localhost: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_cache_bypass $http_upgrade; # Importante: timeouts largos para webhooks y SSE proxy_read_timeout 86400; proxy_send_timeout 86400; } }
Activa el sitio y obtén el certificado SSL:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx # Obtener certificado — sigue las instrucciones interactivas sudo certbot --nginx -d n8n.tudominio.com # La renovación automática ya está configurada por certbot sudo systemctl status certbot.timer
Paso 6: Arrancar y verificar
cd ~/n8n docker compose up -d # Ver logs docker compose logs -f n8n # Verificar que está escuchando curl -I https://n8n.tudominio.com
Accede a https://n8n.tudominio.com — la primera vez te pide crear el usuario admin. Hazlo con email real (n8n permite recuperación) y password fuerte.
Paso 7: Backups automáticos
Sin backups, todo lo anterior es inútil el día que algo falle. La estrategia mínima viable: dump diario de PostgreSQL subido a almacenamiento externo (S3, Backblaze B2, R2 de Cloudflare).
# /home/deploy/scripts/backup-n8n.sh #!/bin/bash DATE=$(date +%Y%m%d-%H%M%S) BACKUP_DIR=/home/deploy/backups mkdir -p $BACKUP_DIR # Dump de PostgreSQL docker compose -f /home/deploy/n8n/docker-compose.yml exec -T postgres \ pg_dump -U n8n n8n | gzip > "$BACKUP_DIR/n8n-$DATE.sql.gz" # Subir a Backblaze B2 (o S3) b2 upload-file mi-bucket "$BACKUP_DIR/n8n-$DATE.sql.gz" backups/ # Limpiar backups locales mayores de 7 días find $BACKUP_DIR -type f -mtime +7 -delete
Programa con cron a las 3am cada día:
0 3 * * * /home/deploy/scripts/backup-n8n.sh >> /var/log/n8n-backup.log 2>&1
Operación día a día
Actualizar n8n a una versión nueva
cd ~/n8n docker compose pull n8n docker compose up -d n8n docker image prune -f
Ver consumo de recursos
docker stats --no-stream
Restaurar desde backup
gunzip -c n8n-20260505-030001.sql.gz | docker compose exec -T postgres psql -U n8n n8n
¿Necesitas un n8n robusto en tu infraestructura?
Despliegue completo con backups, monitorización y workflows iniciales. Listo para producción en 48h en tu propio servidor.
Hablamos de tu setup →