Skip to main content
โšก Calmops

n8n Self-Hosted Production Guide: Docker, Scaling, and Security

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 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.

Resources

Comments