Introduction
Running n8n in production requires careful consideration of security, reliability, and scalability. While n8n Cloud offers a managed solution, self-hosting provides complete control over your data and infrastructure. This guide covers everything you need to deploy n8n in a production environment.
Deployment Options
Quick Comparison
| Method | Complexity | Cost | Control | Best For |
|---|---|---|---|---|
| Docker | Low | $ | High | Small teams |
| Docker Compose | Medium | $$ | High | Medium teams |
| Kubernetes | High | $$$ | Full | Enterprise |
| Bare Metal | Medium | $$ | Full | Compliance needs |
Docker Deployment
Basic Docker Setup
# Pull the latest image
docker pull n8nio/n8n:latest
# Run with basic configuration
docker run -d \
--name n8n \
-p 5678:5678 \
-v n8n_data:/home/node/.n8n \
-e N8N_BASIC_AUTH_ACTIVE=true \
-e N8N_BASIC_AUTH_USER=admin \
-e N8N_BASIC_AUTH_PASSWORD=your_secure_password \
n8nio/n8n:latest
Production Docker Compose
# docker-compose.yml
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_HOST=0.0.0.0
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
# Authentication
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
# Security
- N8N_JWT_AUTH_ACTIVE=true
- WEBHOOK_URL=https://n8n.yourdomain.com/
# Database
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
# Execution
- EXECUTIONS_MODE=regular
- EXECUTIONS_TIMEOUT=300
- EXECUTIONS_TIMEOUT_MAX=600
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
- redis
postgres:
image: postgres:15
container_name: n8n-postgres
restart: unless-stopped
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: n8n-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
n8n_data:
postgres_data:
redis_data:
Environment Variables Reference
# Core Settings
N8N_HOST=0.0.0.0
N8N_PORT=5678
N8N_PROTOCOL=https
NODE_ENV=production
# Authentication
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=secure_password
N8N_JWT_AUTH_ACTIVE=true
N8N_JWT_SECRET=your_jwt_secret
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=postgres_password
# Execution Settings
EXECUTIONS_MODE=regular # or queue
EXECUTIONS_TIMEOUT=300 # 5 minutes
EXECUTIONS_TIMEOUT_MAX=600
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_ON_SUCCESS=all
# Queue (for scaling)
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
# Webhooks
WEBHOOK_URL=https://n8n.yourdomain.com/
WEBHOOK_METHOD=post
# Encryption
N8N_ENCRYPTION_KEY=your_32_char_encryption_key
Database Configuration
PostgreSQL (Recommended for Production)
# PostgreSQL with connection pool
postgres:
image: postgres:15
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
command:
- postgres
- -c
- max_connections=200
- -c
- shared_buffers=256MB
Migration from SQLite
# Export from SQLite
docker exec -it n8n n8n export:workflow --backup --output /home/node/.n8n/backups/
# Import to PostgreSQL
# Update environment variables and restart
# n8n will automatically migrate on first startup
Security Hardening
Reverse Proxy with Nginx
# /etc/nginx/sites-available/n8n
server {
listen 443 ssl http2;
server_name n8n.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://localhost:5678;
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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
SSL with Let’s Encrypt
# Certbot installation
sudo apt install certbot python3-certbot-nginx
# Generate certificate
sudo certbot --nginx -d n8n.yourdomain.com
# Auto-renewal
sudo certbot renew --dry-run
Security Headers
# Add security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline'" always;
Authentication Options
# Basic Auth (built-in)
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=password
# SSO with OIDC
N8N_SSO_ENABLED=true
N8N_SSO_CLIENT_ID=your_client_id
N8N_SSO_CLIENT_SECRET=your_client_secret
N8N_SSO_AUTH_ENDPOINT=https://your-idp.com/authorize
N8N_SSO_TOKEN_ENDPOINT=https://your-idp.com/token
# LDAP Integration
N8N_LDAP_ENABLED=true
N8N_LDAP_URL=ldap://ldap.yourdomain.com
N8N_LDAP_SEARCH_BASE=dc=example,dc=com
N8N_LDAP_BIND_DN=cn=admin,dc=example,dc=com
Monitoring and Logging
Health Checks
# Add health check endpoint
environment:
- N8N_METRICS=true
- N8N_METRICS_ENDPOINT=/metrics
# Check health
curl http://localhost:5678/healthz
curl http://localhost:5678/metrics
Logging Configuration
# Structured logging
environment:
- LOG_LEVEL=info
- LOG_OUTPUT=json # For log aggregation
Monitoring with Prometheus
# Prometheus configuration
scrape_configs:
- job_name: 'n8n'
static_configs:
- targets: ['n8n:5678']
Log Aggregation with Loki
# Docker logging to Loki
services:
n8n:
logging:
driver: loki
options:
loki-url: "http://loki:3100/loki/api/v1/push"
loki-external-labels: "service=n8n"
Scaling Strategies
Queue Mode for High Volume
# Enable queue mode
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
- QUEUE_BULL_REDIS_DB=0
Horizontal Scaling
โโโโโโโโโโโโโโโ
โโโโโบโ n8n #1 โ
โ โโโโโโโโโโโโโโโ
โโโโโโโโโโโโ โ โโโโโโโโโโโโโโโ
โ Load โโโโโโโโโโผโโโโบโ n8n #2 โ
โ Balancerโ โ โโโโโโโโโโโโโโโ
โโโโโโโโโโโโ โ โโโโโโโโโโโโโโโ
โโโโโบโ n8n #3 โ
โโโโโโโโโโโโโโโ
โ
โโโโโโดโโโโโ
โ โ
โโโโโโผโโโ โโโโผโโโโโ
โ Redis โ โPostgresโ
โ Queue โ โ DB โ
โโโโโโโโโ โโโโโโโโโโ
Worker Configuration
# Separate worker for execution
services:
n8n-worker:
image: n8nio/n8n:latest
command: worker
environment:
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
deploy:
replicas: 3
Backup and Recovery
Database Backup
# PostgreSQL backup
docker exec n8n-postgres pg_dump -U n8n n8n > backup_$(date +%Y%m%d).sql
# Automated backup script
#!/bin/bash
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# Backup database
docker exec n8n-postgres pg_dump -U n8n n8n > $BACKUP_DIR/n8n_$DATE.sql
# Backup workflows
docker exec n8n tar -czf /tmp/workflows_backup.tar.gz -C /home/node/.n8n workflows/
# Cleanup old backups (keep 7 days)
find $BACKUP_DIR -type f -mtime +7 -delete
Restore Procedure
# Restore database
docker exec -i n8n-postgres psql -U n8n n8n < backup_20260310.sql
# Restore workflows
docker exec n8n tar -xzf /tmp/workflows_backup.tar.gz -C /home/node/.n8n/
Performance Optimization
Resource Allocation
# Production resource limits
services:
n8n:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
Caching
# Add Redis caching
environment:
- N8N_REDIS_HOST=redis
- N8N_REDIS_PORT=6379
Database Connection Pooling
# PostgreSQL optimized settings
command:
- postgres
- -c
- max_connections=200
- -c
- shared_buffers=512MB
- -c
- effective_cache_size=1GB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=256MB
Maintenance
Updates and Upgrades
# Pull latest image
docker pull n8nio/n8n:latest
# Backup before update
docker exec n8n-postgres pg_dump -U n8n n8n > backup_pre_update.sql
# Restart with new image
docker-compose down
docker-compose up -d
# Check logs
docker logs -f n8n
Troubleshooting Common Issues
| Issue | Solution |
|---|---|
| Slow performance | Increase memory, enable caching |
| Webhook timeouts | Increase timeout settings |
| Database connection errors | Check connection pool settings |
| Memory leaks | Restart container, update to latest version |
| WebSocket disconnects | Check reverse proxy configuration |
Disaster Recovery
High Availability Setup
# docker-compose.ha.yml
version: '3.8'
services:
n8n-primary:
image: n8nio/n8n:latest
volumes:
- n8n_data:/home/node/.n8n
deploy:
replicas: 2
haproxy:
image: haproxy:latest
ports:
- "5678:5678"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
Checklist
Production Deployment Checklist:
- [ ] SSL/TLS configured
- [ ] Authentication enabled
- [ ] Database backed up
- [ ] Health checks configured
- [ ] Monitoring set up
- [ ] Log aggregation working
- [ ] Backup strategy implemented
- [ ] Resource limits set
- [ ] Security headers added
- [ ] Update strategy defined
Conclusion
Self-hosting n8n in production provides full control over your automation infrastructure. Start with Docker Compose for simplicity, then scale as needed. Remember to implement proper security, monitoring, and backup strategies from the beginning.
Related Articles
- n8n Complete Guide: AI-Powered Workflow Automation
- n8n Advanced Workflow Patterns
- n8n Webhooks and API Integration
Comments