← Back to Home

n8n Self-Hosted Production: 5 Critical Pitfalls and How I Fixed Them

n8ndockerself-hostedworkflow automationPostgreSQL

# n8n Self-Hosted 5 Real Production Pitfalls: SQLite/SSL/Permissions/Backup/Timeout

This article contains affiliate links (n8n itself is free and open-source; Docker/PostgreSQL tools mentioned may have affiliate programs).

Self-hosting n8n for workflow automation sounds straightforward: one Docker command, configure a few nodes, and your automation is running. But when you actually push it to production, 5 "looks fine but explodes on deployment" problems show up one by one: SQLite data loss, SSL certificate issues, container running as root, backups that were never tested, and execution timeouts silently killing workflows.

This is a real case review of those 5 pitfalls, each with specific error messages, the troubleshooting process, and reproducible fixes.

n8n Production Architecture Overview

Before diving into pitfalls, here's the minimum production architecture:

User → Nginx(443) → n8n(:5678) → PostgreSQL(:5432)
                           ↓
                    n8n_files/   ← attachments/binary storage

Quick start with Docker Compose:

version: '3.8'
services:
  n8n:
    image: n8nio/n8n:1.65.0
    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_user
      - DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - WEBHOOK_URL=https://your-domain.com/
      - EXECUTIONS_MODE=regular
      - EXECUTIONS_TIMEOUT=600
      - GENERIC_TIMEZONE=Asia/Shanghai
    volumes:
      - n8n_files:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n_user
      - POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n_user -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  n8n_files:
  postgres_data:

.env file:

N8N_DB_PASSWORD=your_secure_random_password_32chars
N8N_ENCRYPTION_KEY=another_32_char_random_key_for_encryption

---

💣 Pitfall 1: SQLite in Production Causes Data Loss

Specific Error

ERROR: Database lock timeout
SQLITE_BUSY: database is locked

Or execution history "disappearing" — after redeployment, all historical records are gone.

Root Cause

n8n uses SQLite by default, which is fine for development/testing but has serious production risks:

Debugging Commands

# Check n8n database file size (SQLite mode)
ls -lh /home/node/.n8n/database.sqlite

# Check if running inside Docker (temporary fix)
docker exec -it  ls -la /home/node/.n8n/

Solution

Migrate to PostgreSQL immediately:

# 1. Export data from old container (if available)
docker exec -it n8n_old n8n export:workflows --backupFile /tmp/workflows.json

# 2. Rebuild with PostgreSQL docker-compose
# (see docker-compose.yaml example above)

# 3. Import workflows after startup
docker exec -i n8n n8n import:workflows --input /tmp/workflows.json

Configure PostgreSQL connection (docker-compose environment variables):

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres        # Docker Compose service name
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n_user
DB_POSTGRESDB_PASSWORD=xxx

Verify database type:

Go to n8n Settings → Source Control, confirm it shows PostgreSQL 16.x not SQLite 3.x.

Prevention

---

💣 Pitfall 2: Nginx Reverse Proxy Causes Webhook 502/404

Specific Error

# Browser shows n8n homepage fine, but webhook calls fail:
502 Bad Gateway
# or
404 Not Found

Manual trigger shows nothing in the webhook logs.

Root Cause

Nginx location block is misconfigured, or WEBHOOK_URL environment variable is wrong. n8n's webhook path uses /webhook/ prefix — Nginx must pass this through correctly:

# ❌ Wrong configuration (misses /webhook/ path)
location / {
    proxy_pass http://127.0.0.1:5678;
}

# ✅ Correct configuration
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 (n8n editor live preview needs this)
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    # Critical: increase timeout
    proxy_read_timeout 300s;
    proxy_connect_timeout 75s;
}

# Webhook-dedicated path (if using separate domain)
location /webhook/ {
    proxy_pass http://127.0.0.1:5678;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_read_timeout 300s;
}

Debugging Steps

# 1. Confirm n8n process is healthy inside container
docker exec -it n8n curl -s http://localhost:5678 | head -20

# 2. Confirm WEBHOOK_URL is a publicly accessible full URL
docker exec -it n8n env | grep WEBHOOK
# Correct format: https://n8n.your-domain.com/

# 3. Test webhook reachability from public internet
curl -X POST https://n8n.your-domain.com/webhook/test/your-webhook-id -v

Solution

Add to n8n service in docker-compose.yaml:

environment:
  - WEBHOOK_URL=https://n8n.your-domain.com/
  # NOT http://localhost:5678 — that's the internal container address

Then reload Nginx:

nginx -t && nginx -s reload

Prevention

---

💣 Pitfall 3: Execution Timeout Silently Kills Workflows

Specific Error

Execution timed out after 300 seconds
Workflow "XXX" was running for longer than the configured timeout of 300 seconds.

The workflow logic is correct but it just stops halfway through, and the error log only shows "timed out."

Root Cause

n8n's default execution timeout is 300 seconds (5 minutes), which is far too short for workflows that call external APIs or process bulk data. But this timeout setting is buried deep in the configuration — many people don't even know it exists.

Debugging Commands

# Check current timeout configuration
docker exec -it n8n env | grep EXECUTIONS_TIMEOUT
# Output may be empty (default 300)

# Check recent timeout logs
docker logs n8n --since 10m | grep -i timeout

Solution

**Option 1: Change global default timeout** (docker-compose.yaml):

environment:
  - EXECUTIONS_TIMEOUT=600        # 10 minutes (600 seconds)
  # Or set to 0 for unlimited (use with caution)

Option 2: Change individual workflow timeout (n8n UI):

1. Open workflow → Click Workflow Settings (top right)

2. Find Execution Timeout

3. Set to 600 (seconds) or check **Allow manual executions to run longer**

Option 3: Per-node API timeout (most commonly overlooked):

Each HTTP Request node has its own timeout (default 300 seconds), configured in node settings:

Options → Timeout (ms): 600000  ← 10 minutes

Prevention

// Add an Error Trigger at the end of a workflow
// Sends email notification when timeout occurs

---

💣 Pitfall 4: Backups Were Never Tested — Data Loss Discovered Too Late

Specific Error

The most painful category — after server migration or reinstallation, when trying to restore n8n data:

# Backup file won't open
Error: invalid tar header

# Backup file is empty
ls -lh n8n_backup.tar.gz
# -rw-r--r--  1 root root     0 Jan  1 00:00 n8n_backup.tar.gz

# PostgreSQL backup missing credentials
# Credentials are stored encrypted separately — exporting workflows alone is not enough

Root Cause

Three common backup mistakes:

1. Only backing up workflows, not credentials: n8n credentials (API keys, database passwords, etc.) are stored encrypted separately in PostgreSQL — exporting workflows is not enough

2. **Backup script has no execute permission**: forgot chmod +x backup.sh

3. Backup goes to container instead of mounted volume: deleted when container is removed

Correct Backup Solution

Complete backup script (workflows + credentials + PostgreSQL):

#!/bin/bash
# n8n_backup.sh — run on the host machine, not inside container

BACKUP_DIR="/opt/n8n_backups"
DATE=$(date +%Y%m%d_%H%M%S)
CONTAINER_NAME="n8n"

mkdir -p $BACKUP_DIR

# 1. Export all workflows (JSON format)
docker exec $CONTAINER_NAME n8n export:workflows --output /tmp/workflows_$DATE.json

# 2. Export all credentials (encrypted, needed for migration)
docker exec $CONTAINER_NAME n8n export:credentials --output /tmp/credentials_$DATE.json

# 3. PostgreSQL full backup
docker exec postgres pg_dump -U n8n_user n8n > $BACKUP_DIR/n8n_db_$DATE.sql

# 4. Package everything
tar czf $BACKUP_DIR/n8n_full_backup_$DATE.tar.gz \
    /tmp/workflows_$DATE.json \
    /tmp/credentials_$DATE.json \
    $BACKUP_DIR/n8n_db_$DATE.sql

# 5. Clean up backups older than 7 days
find $BACKUP_DIR -name "n8n_full_backup_*.tar.gz" -mtime +7 -delete

echo "Backup completed: n8n_full_backup_$DATE.tar.gz"

**Set up cron job** (crontab -e):

0 3 * * * /opt/n8n_backup.sh >> /var/log/n8n_backup.log 2>&1

Verify backup validity (critical — don't skip this):

# 1. Verify tar package integrity
tar tzf n8n_full_backup_20260615_030000.tar.gz

# 2. Verify workflow JSON is readable
cat /tmp/workflows_20260615_030000.json | python3 -m json.tool > /dev/null && echo "JSON OK"

# 3. Verify SQL backup
grep -c "COPY" n8n_db_20260615_030000.sql   # Should be > 0

Prevention

---

💣 Pitfall 5: No Error Notifications — Silent Workflow Failures

Specific Error

This category has no clear error message, but causes the biggest business impact — workflows run daily but some nodes quietly fail, n8n status looks green, and you assume everything is fine.

Root Cause

n8n does not send execution result notifications by default. Many workflows run in "silent failure" mode:

Debugging Method

Check Execution History in n8n:

1. Settings → Source Control → Executions

2. Filter by Status: Error

3. Find the failing workflow, check which node failed

# Check last 24h execution records (via API)
curl -s -u admin:${N8N_API_KEY} \
  "https://n8n.your-domain.com/rest/executions?limit=50&filter={}" \
  | jq '.data[] | {id, workflowId, status, finished, startTime}'

Solution

Add Error Trigger to every workflow (built-in n8n node):

1. Open workflow → Click + node selector

2. Search for Error Trigger → Add it

3. Connect Error Trigger to Send Email or Slack Message node

Example configuration:

[Any Node] → (on error) → Error Trigger → Slack Message
                              ↓
                        Email Notification
                        Subject: Workflow {workflow.name} failed on {date}
                        Node: {node.name}
                        Error: {error.message}

**Configure global error alerts** (in docker-compose.yaml):

environment:
  # Email notification on workflow execution failure
  - N8N_EMAIL_MODE=smtp
  - N8N_SMTP_HOST=smtp.example.com
  - N8N_SMTP_PORT=587
  - N8N_SMTP_USER=noreply@example.com
  - N8N_SMTP_PASS=xxx
  - N8N_SMTP_FROM=n8n@example.com

Prevention

environment:
  - N8N_METRICS=true

---

Complete Production Checklist

Save this checklist and review it before every deployment:

□ PostgreSQL instead of SQLite
□ N8N_ENCRYPTION_KEY is a unique 32-char random key (never reuse)
□ WEBHOOK_URL is set to public HTTPS URL
□ Nginx proxy passes /webhook/ path and WebSocket support is enabled
□ EXECUTIONS_TIMEOUT=600 or higher (adjust as needed)
□ Backup script is configured and recovery has been tested
□ Error Trigger notifications are configured
□ N8N_METRICS=true is enabled and connected to monitoring
□ SSL certificate auto-renews via Let's Encrypt / Certbot
□ Database and n8n_files use named volumes, not bind mounts

---

Next Steps

Once your self-hosted n8n is stable, you can expand further:

---

👉 Want low-cost AI workflow access? MiniMax Token Plan provides stable API calls, works great with n8n and AI toolchains:

https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link

📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews

🔗 Recommended Tools

These are carefully selected tools. Using our affiliate links supports us to keep producing quality content:

☁️ DigitalOcean Cloud ⚡ Vultr VPS 📚 WordPress Books 🔍 WordPress SEO Books 🌐 Web Hosting Books 🐳 Docker Books 🐧 Linux Books 🐍 Python Books 💰 Affiliate Marketing 💵 Passive Income Books 🖥️ Server Books ☁️ Cloud Computing Books 🚀 DevOps Books ⭐ MiniMax Token Plan 🔍 Cloud Search
← Back to Home