Self-Hosted n8n Server Setup
Self-hosted n8n gives full data control, unlimited workflows and operations, ability to integrate with internal services without VPN.
Requirements
- VPS/server: minimum 2 vCPU, 2 GB RAM (recommended 4 GB with >50 active workflows)
- Docker + Docker Compose
- PostgreSQL 12+ (instead of built-in SQLite for production)
- SSL certificate (Let's Encrypt)
docker-compose.yml (Production)
version: '3.8'
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://n8n.example.com/
- GENERIC_TIMEZONE=Europe/Moscow
# PostgreSQL
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
# Encrypt credentials
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
# Email for error notifications
- N8N_EMAIL_MODE=smtp
- N8N_SMTP_HOST=smtp.example.com
- N8N_SMTP_PORT=587
- [email protected]
- N8N_SMTP_PASS=${SMTP_PASSWORD}
# Queue for scaling
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- N8N_CONCURRENCY_PRODUCTION_LIMIT=10
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
n8n-worker:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
command: worker
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- QUEUE_BULL_REDIS_HOST=redis
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
depends_on:
- n8n
- redis
postgres:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_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
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
volumes:
n8n_data:
postgres_data:
Nginx Reverse Proxy + SSL
server {
listen 443 ssl;
server_name n8n.example.com;
ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# For long workflows
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
Backup
#!/bin/bash
# backup-n8n.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/backups/n8n
# PostgreSQL dump
docker exec n8n-postgres-1 pg_dump -U n8n n8n > "$BACKUP_DIR/n8n_db_$DATE.sql"
# Backup credentials and workflows
docker cp n8n-n8n-1:/home/node/.n8n "$BACKUP_DIR/n8n_files_$DATE"
# Upload to S3
aws s3 cp "$BACKUP_DIR/n8n_db_$DATE.sql" "s3://backups-bucket/n8n/"
# Delete backups older than 30 days
find $BACKUP_DIR -mtime +30 -delete
Monitoring
n8n exports Prometheus metrics at /metrics:
# prometheus.yml
scrape_configs:
- job_name: 'n8n'
static_configs:
- targets: ['n8n:5678']
metrics_path: /metrics
Timeline
Basic install with Docker + PostgreSQL + Nginx + SSL — 1 day. Worker setup, monitoring, backups — another 1–2 days.







