n8n is one of the strongest open-source alternatives to Zapier and Make. Self-hosting gives you unlimited workflow executions, data sovereignty for sensitive integrations, and per-month costs that don't grow with your usage. This guide walks through a production setup on a VPS, not a quick Docker one-liner that breaks under real load.

We run n8n internally for billing automation, monitoring, and customer onboarding flows. The setup below is what we recommend to customers who want to do the same.

Why production setup matters

The default n8n Docker image works fine for a single user testing workflows. Once you put it in front of webhooks from production systems, run scheduled flows, or onboard a team, you need:

  • PostgreSQL backend instead of the default SQLite, which locks under concurrent writes.
  • Queue mode with Redis so long-running workflows don't block the main process.
  • Reverse proxy with HTTPS for secure webhook endpoints.
  • Persistent storage and backups because losing workflow definitions is painful.
  • Secret management so credentials aren't sitting in plaintext env files.

A weekend spent on these pays off the first time a scheduled workflow needs to run during a 3 AM deployment.

VPS sizing for n8n

Sizing depends on workflow concurrency and complexity. A reasonable starting point:

  • Small team, under 50 workflows, occasional webhooks: 2 vCPU, 4 GB RAM, 50 GB disk.
  • Medium use, frequent webhooks, scheduled flows every few minutes: 4 vCPU, 8 GB RAM, 100 GB disk.
  • Heavy use, queue mode with multiple workers, large payloads: 8 vCPU, 16 GB RAM, 200 GB disk.

Our Cloud VPS plans map cleanly to these tiers. For CPU-bound workflows (heavy data transformation, AI/LLM calls), the AMD VPS line is faster per core.

Prerequisites

This guide assumes:

  • Fresh Ubuntu 24.04 LTS or Debian 12 VPS.
  • A domain or subdomain pointed at the VPS IP (e.g. n8n.yourdomain.com).
  • Basic SSH access as a non-root user with sudo.
  • Port 80 and 443 open in the firewall.

Step 1: Install Docker and Docker Compose

# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker via the official repository
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# Add your user to the docker group
sudo usermod -aG docker $USER
newgrp docker

# Verify
docker run hello-world

Avoid the convenience get.docker.com script for production. Repository-based installs receive proper security updates.

Step 2: Create the project structure

mkdir -p ~/n8n/{data,postgres,redis,backups}
cd ~/n8n

Create an environment file for secrets:

cat > .env << 'EOF'
# Domain
N8N_HOST=n8n.yourdomain.com
WEBHOOK_URL=https://n8n.yourdomain.com/
N8N_PROTOCOL=https

# Database
POSTGRES_USER=n8n
POSTGRES_PASSWORD=GENERATE_STRONG_PASSWORD_HERE
POSTGRES_DB=n8n

# Encryption key (32+ chars, generate once and never change)
N8N_ENCRYPTION_KEY=GENERATE_RANDOM_32_CHAR_STRING_HERE

# Timezone
GENERIC_TIMEZONE=Europe/Ljubljana
TZ=Europe/Ljubljana

# Queue mode
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379

# Security
N8N_BASIC_AUTH_ACTIVE=false
N8N_USER_MANAGEMENT_DISABLED=false
EOF

chmod 600 .env

Generate secure values:

# PostgreSQL password
openssl rand -base64 32

# Encryption key (32 hex chars)
openssl rand -hex 32

Copy each output into the .env file. The encryption key must never change after first use, otherwise credentials stored in workflows become unreadable.

Step 3: Create docker-compose.yml

cat > docker-compose.yml << 'EOF'
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
      - POSTGRES_NON_ROOT_USER=${POSTGRES_USER}
      - POSTGRES_NON_ROOT_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - ./redis:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_ENCRYPTION_KEY
      - N8N_HOST
      - N8N_PROTOCOL
      - WEBHOOK_URL
      - GENERIC_TIMEZONE
      - TZ
      - EXECUTIONS_MODE
      - QUEUE_BULL_REDIS_HOST
      - QUEUE_BULL_REDIS_PORT
      - N8N_RUNNERS_ENABLED=true
    volumes:
      - ./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_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_ENCRYPTION_KEY
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - GENERIC_TIMEZONE
      - TZ
    volumes:
      - ./data:/home/node/.n8n
    depends_on:
      - n8n
EOF

Two important details:

  1. The main n8n container binds to 127.0.0.1:5678, not 0.0.0.0. Only the local reverse proxy can reach it.
  2. The worker service consumes the queue. Add more replicas with docker compose up -d --scale n8n-worker=3 once you understand your workload.

Step 4: Reverse proxy with Nginx and Let's Encrypt

sudo apt install -y nginx certbot python3-certbot-nginx

Create the Nginx config:

sudo tee /etc/nginx/sites-available/n8n << 'EOF'
server {
    listen 80;
    server_name n8n.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name n8n.yourdomain.com;

    # SSL config added by certbot

    client_max_body_size 50M;

    location / {
        proxy_pass http://127.0.0.1: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 for long-running workflows
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# Issue SSL certificate
sudo certbot --nginx -d n8n.yourdomain.com

The client_max_body_size 50M and extended timeouts matter when workflows handle file uploads or wait on slow external APIs.

Step 5: Start n8n

cd ~/n8n
docker compose up -d

# Watch logs until everything is healthy
docker compose logs -f

Visit https://n8n.yourdomain.com and create the owner account. The user management system is enabled by default in recent versions.

Step 6: Webhook security

Webhooks are public by design, but unauthenticated webhooks invite abuse. Two patterns work well:

1. Header-based authentication. In the Webhook node, set "Authentication" to "Header Auth" and require a secret token. External systems send the token in a custom header.

2. Path-based obscurity for low-risk webhooks. Use a UUID in the webhook path. Don't rely on this alone for anything sensitive.

For incoming webhooks from services like GitHub or Stripe, validate the signature header. n8n has dedicated nodes for this.

Step 7: Backups

Two things need backing up: PostgreSQL and the ./data directory.

Create a backup script:

cat > ~/n8n/backup.sh << 'EOF'
#!/bin/bash
set -e

BACKUP_DIR="$HOME/n8n/backups"
DATE=$(date +%Y%m%d-%H%M%S)
cd "$HOME/n8n"

# Database dump
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > "$BACKUP_DIR/db-$DATE.sql.gz"

# n8n data directory (workflows, credentials, encryption key)
tar -czf "$BACKUP_DIR/data-$DATE.tar.gz" data/

# Keep last 14 days
find "$BACKUP_DIR" -name "*.gz" -mtime +14 -delete

echo "Backup complete: $DATE"
EOF

chmod +x ~/n8n/backup.sh

# Schedule daily at 3 AM
(crontab -l 2>/dev/null; echo "0 3 * * * $HOME/n8n/backup.sh >> $HOME/n8n/backups/backup.log 2>&1") | crontab -

For off-server backups, push the backup directory to an S3-compatible store or rsync to a Proxmox Backup Server. Customers on our infrastructure can use the included PBS backup space.

Step 8: Updates

cd ~/n8n
docker compose pull
docker compose up -d
docker image prune -f

n8n releases frequently. Subscribe to the changelog and test major version bumps in a staging instance before updating production. Always take a fresh backup before updating.

Common pitfalls

Webhooks not reachable. Check WEBHOOK_URL matches your public domain and includes the trailing slash. Verify Nginx logs for 502 errors that indicate the container isn't responding.

Workflows hang at 100%. Queue mode is misconfigured. Verify Redis is reachable and at least one worker is running with docker compose logs n8n-worker.

Credentials become "Invalid" after a restart. The encryption key changed. Restore your .env file from backup. Never let this key rotate without re-entering every credential.

High memory use on the main container. Long-running workflows aren't moving to workers. Confirm EXECUTIONS_MODE=queue is set on the main container, not just the worker.

TLS issues with webhook callers. Some old API clients don't follow redirects. Make sure WEBHOOK_URL uses https:// and your DNS A record matches.

FAQ

Is self-hosted n8n really free?

The Community Edition is free for any use under the Sustainable Use License. Commercial features like SSO, LDAP, and external secret stores require an Enterprise license. For most teams, Community is enough.

How does this compare to n8n Cloud?

Self-hosting costs roughly 5 to 15 EUR per month for the VPS versus 24 EUR per month for n8n Cloud Starter, with no execution limits. The trade-off is operational responsibility: you handle updates, backups, and uptime.

Can I run n8n without queue mode?

Yes, for light usage. Remove the n8n-worker service and set EXECUTIONS_MODE=regular. Once you have more than a handful of concurrent workflows or long-running flows, queue mode is worth the small extra complexity.

Does n8n work with European data residency requirements?

Self-hosting on an EU-located VPS like ours keeps all workflow data, credentials, and execution logs in the EU. This is one of the main reasons GDPR-conscious teams move from cloud SaaS to self-hosted n8n.

Run n8n on infrastructure built for it

Our VPS plans run on Ceph-backed storage with snapshot support, which makes the backup and rollback workflow described above significantly faster. EU-located, multilingual support, no surprise resource throttling.

Order a Cloud VPS or AMD VPS and you'll have n8n running on a production-grade setup before your morning coffee is cold.

War diese Antwort hilfreich? 0 Benutzer fanden dies hilfreich (0 Stimmen)