n8n 工作流自动化 Self-Hosted Docker 容器化部署 Deployment Troubleshooting Guide
n8n is an open-source workflow automation tool with excellent Docker self-hosting support. When I deployed n8n on an Ubuntu 开发环境 24.04 VPS 配置避坑, it took me 2 full days to get a production-ready setup. This article documents the 5 traps I hit and how I resolved each one.
All information is verified against the official n8n documentation (docs.n8n.io) and my actual test environment.
Trap 1: SQLite to PostgreSQL Migration — Missing the Key Environment Variable
n8n defaults to SQLite, storing data in a database file under /home/node/.n8n. When I tried switching to PostgreSQL, I configured the database connection in docker-compose.yml, but the container kept throwing ECONNREFUSED.
My broken configuration:
environment:
- DB_TYPE=postgres
- DB_POSTGRESDB=n8n
- DB_POSTGRESHOST=postgres
- DB_POSTGRESUSER=n8n
- DB_POSTGRESPASSWORD=xxx
Container log error:
Database is locked
Cannot start n8n
Root cause: n8n's documentation clearly states that when using PostgreSQL, you **must** also set the DB_ADAPTER variable — otherwise n8n ignores all other DB variables and continues trying to read/write SQLite, locking the database file.
Correct configuration:
environment:
- DB_TYPE=postgresdb
- DB_ADAPTER=postgres
- DB_POSTGRESDB=n8n
- DB_POSTGRESHOST=postgres
- DB_POSTGRESUSER=n8n
- DB_POSTGRESPASSWORD=${DB_PASSWORD}
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
**Key point**: DB_ADAPTER=postgres is a documented requirement, yet many third-party tutorials omit it entirely.
Additionally, the PostgreSQL container's startup order matters — n8n must wait for PostgreSQL to be fully ready before starting. Add depends_on with a health check:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- n8n_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n
depends_on:
postgres:
condition: service_healthy
Trap 2: Scheduled Tasks Not Running — Inconsistent Timezone Configuration
I set up a Schedule Trigger to run every morning at 9 AM, but it actually executed at 1 AM — an 8-hour offset.
The issue: Docker containers have an isolated timezone from the host. I had set Asia/Shanghai on the host machine, but the TZ environment variable wasn't properly passed into the container.
**Wrong approach**: Setting only GENERIC_TIMEZONE in docker-compose.yml, ignoring the container's internal system timezone.
Correct approach — set both environment variables:
environment:
- GENERIC_TIMEZONE=Asia/Shanghai
- TZ=Asia/Shanghai
GENERIC_TIMEZONE controls n8n's internal scheduler calculations. TZ controls the container system's timezone. They must match, or scheduled triggers will fire at the wrong time.
Verification method — check actual time inside the container:
docker exec -it n8n date
Trap 3: Webhooks Not Found Behind Reverse Proxy — N8N_WEBHOOK_URL Must Be Set
Running n8n on port 5678, proxied through Nginx at n8n.example.com. The web UI loaded fine, but creating a Webhook node and visiting the trigger URL returned 404.
Nginx configuration:
location / {
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Problem: Without an explicit N8N_WEBHOOK_URL, n8n defaults to http://localhost:5678, causing relative path errors when accessed through Nginx.
Solution: Explicitly set the Webhook URL in docker-compose.yml:
environment:
- N8N_WEBHOOK_URL=https://n8n.example.com/
- WEBHOOK_URL=https://n8n.example.com/
Note: the trailing slash on the URL matters. Restart the container after setting this:
docker compose down && docker compose up -d
Trap 4: Workflow Execution Timeout — Long Workflows Mysteriously Die
A data sync workflow that needs 5 minutes kept dying around the 30-second mark. Log output:
Execution timed out after 30 seconds
n8n's default execution timeout is 30 seconds. Workflows processing large files or calling slow external APIs commonly hit this limit.
Fix: Adjust the timeout via environment variable, or override per-workflow:
environment:
# Default execution timeout in milliseconds — 10 minutes here
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# Disable timeout entirely (use carefully in production)
- N8N_EXECUTIONS_TIMEOUT=0
# Or set a specific timeout (300000ms = 5 minutes)
- N8N_EXECUTIONS_TIMEOUT=300000
The safer approach is setting timeout per-workflow rather than disabling it globally. Open the workflow → Settings → Execution Settings → Custom Timeout.
Trap 5: Credentials Lost After Restart — N8N_ENCRYPTION_KEY Must Be Persisted
n8n encrypts all credentials (API keys, passwords, etc.) stored in the database using the key specified by the N8N_ENCRYPTION_KEY environment variable. After restarting the container, all previously configured API credentials stopped working.
Cause: N8N_ENCRYPTION_KEY wasn't persisted. If docker-compose doesn't properly pass this variable on restart, n8n generates a new random key, making it impossible to decrypt existing credentials.
**Correct approach**: Store the encryption key in a .env file (keep it secure) and reference it in docker-compose.yml:
# .env file
N8N_ENCRYPTION_KEY=your-very-long-random-string-here-min-32-chars
DB_PASSWORD=your-postgres-password
ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
environment:
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
Generate a strong random key:
openssl rand -hex 32
**Warning**: Changing N8N_ENCRYPTION_KEY will make all existing credentials unreadable. To rotate the key, export all credentials first and re-import after the change.
Complete Correct docker-compose.yml
Here's my final production-ready configuration that addresses all traps:
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- n8n_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
n8n:
image: docker.n8n.io/n8nio/n8n
ports:
- "127.0.0.1:5678:5678"
environment:
- GENERIC_TIMEZONE=Asia/Shanghai
- TZ=Asia/Shanghai
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- N8N_RUNNERS_ENABLED=true
- N8N_WEBHOOK_URL=https://n8n.example.com/
- DB_TYPE=postgresdb
- DB_ADAPTER=postgres
- DB_POSTGRESDB=n8n
- DB_POSTGRESHOST=postgres
- DB_POSTGRESUSER=n8n
- DB_POSTGRESPASSWORD=${DB_PASSWORD}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_EXECUTIONS_TIMEOUT=300000
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
volumes:
n8n_data:
n8n_pgdata:
Companion Nginx reverse proxy (critical settings):
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 — required for n8n editor real-time updates
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Who Is This For
Good candidates for n8n self-hosting:
- Need to automate 50+ workflows — Zapier free tier won't cut it
- Data must stay on your own servers — can't use cloud services
- Comfortable maintaining Docker and databases
- Need to integrate internal systems (GitLab, ERP, private APIs)
Not suitable:
- Fewer than 10 workflows — Zapier free plan handles this fine
- No server administration experience — expect plug-and-play
- Need SLA guarantee and 24/7 support — use n8n Cloud instead
Cost Reference
Minimum cost for self-hosted n8n:
| Option | Monthly Cost | Specs |
|---|---|---|
| VPS (minimum) | $6-10 | 1C1G |
| VPS (recommended) | $12-20 | 2C2G |
| Add-on: PostgreSQL | $0-5 | Built-in Docker or cloud DB |
Compared to Zapier free (100 executions/month), self-hosted n8n has unlimited executions, and a $12/month VPS offers far better value.
If you're using AI APIs to power n8n workflows — generating content, classifying data, or processing text — the n8n + MiniMax API combination is one of the most cost-effective self-hosted setups available.
👉 Get started now: https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link
Summary: Pre-Deployment Checklist
Before deploying n8n via Docker, confirm each item:
- [ ] PostgreSQL configured with `DB_ADAPTER=postgres`
- [ ] PostgreSQL has `depends_on: condition: service_healthy`
- [ ] Both `GENERIC_TIMEZONE` and `TZ` are set and match
- [ ] `N8N_WEBHOOK_URL` is set (required for reverse proxy setups)
- [ ] `N8N_ENCRYPTION_KEY` is in `.env` and survives container restarts
- [ ] Nginx has WebSocket proxy configured (`proxy_http_version 1.1` + `Upgrade` header)
- [ ] Estimated workflow execution time — set a reasonable `N8N_EXECUTIONS_TIMEOUT`
If your workflows need to call AI APIs to generate content, classify data, or process text, the n8n + MiniMax API combination delivers the best cost-to-performance ratio for self-hosted automation.
👉 Get started now: https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link
🔗 Related Tech Articles
Deep dive into related technical topics: