Cómo desplegar n8n de forma segura en un VPS
Levantar n8n con Docker es fácil.
Dejarlo expuesto en un VPS sin convertirlo en un problema de seguridad ya es otra historia.
El patrón peligroso suele ser este:
- puerto
5678abierto públicamente - credenciales débiles
- sin SSL
- sin reverse proxy
- sin estrategia de backups
Eso puede funcionar para probar. No para operar en serio.
En este tutorial vamos a tomar una instalación básica y llevarla a una forma bastante más razonable para producción.
Qué vas a construir
La arquitectura objetivo será esta:
Internet
↓
Nginx / reverse proxy
↓
HTTPS + dominio
↓
127.0.0.1:5678
↓
n8n en Docker
↓
PostgreSQL
La idea central es simple:
- n8n no debería quedar expuesto directamente a Internet
- el acceso público debería pasar por un dominio con SSL
- la persistencia y autenticación deberían quedar bien definidas
Punto de partida
Si aún no instalaste n8n, primero mira este tutorial:
Ese tutorial te deja una base funcional. Este se enfoca en endurecerla para un entorno real.
Cuándo conviene este enfoque
Tiene sentido cuando:
- ya usas n8n para procesos importantes
- quieres exponer webhooks o usarlo con tu equipo
- necesitas una base más confiable que una demo temporal
No es suficiente cuando:
- tienes requisitos estrictos de alta disponibilidad
- necesitas observabilidad centralizada o despliegues complejos multi-entorno
Pero para la mayoría de instalaciones iniciales, esta base ya marca una diferencia fuerte.
Paso 1: No expongas n8n directamente al mundo
Una mala práctica común:
ports:
- "5678:5678"
Eso deja el servicio disponible públicamente en el puerto del VPS.
Mejor:
ports:
- "127.0.0.1:5678:5678"
Con eso, n8n solo escucha localmente en el servidor, y el acceso externo pasa por tu reverse proxy.
Paso 2: Usa PostgreSQL, no SQLite, si el entorno importa
Para pruebas pequeñas, SQLite puede alcanzar.
Pero si la instancia empieza a ser importante, conviene usar PostgreSQL:
- mejor robustez
- mejor consistencia
- mejor recuperación
- mejor base para crecer
Una configuración mínima sería:
services:
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=tu_password_seguro
- POSTGRES_DB=n8n
volumes:
- db_storage:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n
restart: always
ports:
- "127.0.0.1:5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=tu_password_seguro
Paso 3: Activa autenticación básica
Si vas a exponer el panel de n8n, no puede quedar abierto.
Como mínimo:
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=una_clave_larga_y_unica
No es el mecanismo más sofisticado del mundo, pero es mucho mejor que nada.
Claves importantes:
- no uses
admin/admin - no reutilices contraseñas
- guarda secretos fuera del repo si el entorno ya es serio
Paso 4: Configura dominio y URL pública correctamente
Si n8n va a generar links, callbacks o webhooks, necesita saber cuál es su URL pública real.
Variables típicas:
environment:
- N8N_HOST=n8n.tudominio.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.tudominio.com/
Sin esto, algunos flujos terminan generando rutas incorrectas o inconsistentes.
Paso 5: Coloca un reverse proxy delante
La configuración exacta depende de tu host, pero el patrón debería ser:
https://n8n.tudominio.com -> 127.0.0.1:5678
Eso te permite:
- manejar SSL fuera del contenedor
- centralizar certificados
- controlar acceso
- mantener el servicio interno no expuesto
Paso 6: Usa HTTPS desde el primer día
Si vas a:
- iniciar sesión
- exponer webhooks
- conectar credenciales
- trabajar con datos reales
entonces necesitas HTTPS.
No lo dejes “para después”.
Como mínimo, asegura el dominio con certificados válidos y fuerza redirección a HTTPS en el reverse proxy.
Paso 7: Añade healthchecks y restart policy
No basta con que el contenedor exista. Conviene saber si responde.
Ejemplo simple:
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5678"]
interval: 30s
timeout: 5s
retries: 3
Y usa:
restart: always
Esto no reemplaza monitoreo real, pero mejora bastante el comportamiento operativo inicial.
Paso 8: Haz backups desde temprano
Si n8n importa para tu operación, hay dos cosas que debes poder recuperar:
- la base de datos
- la configuración persistida
Como mínimo, asegúrate de respaldar:
- volumen de PostgreSQL
- volumen o path de configuración de n8n
No esperes al primer error para descubrir que no puedes restaurar nada.
Paso 9: Piensa la seguridad de los webhooks
Abrir n8n al mundo también significa abrir endpoints de webhook.
Eso requiere criterio:
- valida origen cuando aplique
- usa firmas o tokens si la fuente lo soporta
- limita lo que aceptan los workflows
- evita dejar endpoints públicos sin control
No todos los riesgos están en el panel de administración. Muchos viven en los webhooks mal diseñados.
Paso 10: Separa credenciales del código
Una instalación “que funciona” suele terminar con secretos hardcodeados en el compose.
Eso es tolerable para una prueba local. No para producción.
Evolución mínima razonable:
- variables de entorno
- archivos
.env - manejo más serio de secretos si el entorno escala
La regla es simple: si alguien sube ese archivo al repositorio, no deberías comprometer toda la instancia.
Ejemplo de compose más razonable
version: '3.8'
volumes:
db_storage:
n8n_storage:
services:
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=n8n
volumes:
- db_storage:/var/lib/postgresql/data
n8n:
image: docker.n8n.io/n8nio/n8n
restart: always
ports:
- "127.0.0.1:5678:5678"
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_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
- N8N_HOST=n8n.tudominio.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.tudominio.com/
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
- postgres
Errores comunes
1) Dejar 5678 abierto públicamente
Es de los errores más típicos y más innecesarios.
2) Usar credenciales débiles
Si el panel queda expuesto con autenticación trivial, no tienes una instalación segura.
3) No configurar WEBHOOK_URL
Muchos problemas raros de callbacks nacen ahí.
4) No tener backups
Hasta que pierdes workflows o credenciales y descubres que el contenedor “arrancaba bien” pero el negocio no.
5) No controlar endpoints públicos
El panel no es el único vector. Los webhooks también importan.
Qué añadir después
Una vez cubierta esta base, los siguientes pasos razonables serían:
- monitoreo
- backups automatizados
- rotación de logs
- actualizaciones controladas
- entornos separados
- proxy con reglas más finas
No hace falta hacer todo a la vez. Pero sí conviene dejar una instalación que no dependa de suerte.
Resumen
Para desplegar n8n de forma más segura en un VPS necesitas:
- puertos internos, no públicos
- PostgreSQL en lugar de una base frágil
- autenticación básica fuerte
- dominio y HTTPS
- reverse proxy
- backups
- control sobre webhooks y secretos
No es blindaje absoluto. Pero sí te saca del terreno de “instancia expuesta y rezando”.
Si quieres dejar una instalación de n8n bien montada, conectarla con tus procesos o convertir workflows dispersos en automatizaciones mantenibles, puedes escribirme desde Sobre Mí.