Setting up and scaling an enterprise automation stack on self-hosted n8n is a genuinely useful exercise — the flexibility is unmatched. But moving from local testing to production surfaces a specific set of infrastructure problems that don't show up until you're under real load. Here's what I ran into, why it happened, and how I fixed each one.
1. SQLite Concurrency & Database Lock Errors
The problem: Under high-frequency webhooks or concurrent workflow executions, I started seeing database is locked errors, an unresponsive UI, and dropped execution logs.
Root cause: n8n defaults to SQLite for quick starts, which handles concurrent read/write operations poorly once throughput increases.
The fix: Migrated the persistent database layer to external PostgreSQL — DB_TYPE=postgresdb — and tuned the connection pool (DB_POSTGRESDB_POOL_SIZE=10-20) to handle concurrent workloads without locking.
2. Node.js Out-of-Memory Crashes on Large Payloads
The problem: Fetching thousands of records via API nodes, or parsing large CSV/JSON payloads, crashed the main n8n container randomly with Allocation failed — JavaScript heap out of memory.
Root cause: Node.js defaults to roughly a 1.5–2GB heap limit, and n8n keeps execution history in memory unless you configure pruning.
The fix:
- Raised the V8 heap limit:
NODE_OPTIONS="--max-old-space-size=4096" - Enabled automatic execution pruning:
EXECUTIONS_DATA_PRUNE=true,EXECUTIONS_DATA_MAX_AGE=168(7 days) - Used the Loop Over Items / Split in Batches node for large datasets instead of processing one giant array at once
3. Webhook Failures & Missing HTTPS Endpoints
The problem: External services (Stripe, GitHub, custom webhooks) failed to trigger workflows, throwing SSL handshake or CORS errors.
Root cause: Webhooks need a stable public HTTPS endpoint. A mismatched WEBHOOK_URL was causing n8n to hand back internal localhost URLs to external services.
The fix: Put n8n behind a reverse proxy (Nginx/Traefik/Caddy) with automated Let's Encrypt SSL, and set these explicitly:
N8N_HOST=n8n.yourdomain.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourdomain.com/
4. Credentials Break After Container Redeployment
The problem: After pulling a new Docker image or redeploying the stack, every stored credential (API keys, OAuth tokens) became unreadable.
Root cause: n8n generates an ephemeral encryption key automatically if you don't set one. Redeploying the container regenerates that key, which makes every encrypted credential in the database unreadable.
The fix: Set a static N8N_ENCRYPTION_KEY in the environment config and back it up in a secrets manager before the first launch.
5. Main Thread Bottlenecks Under Load
The problem: Long-running Code nodes or heavy HTTP fetches froze the UI and caused incoming webhooks to time out.
Root cause: By default, n8n runs the UI, webhook handling, and workflow execution all in a single-threaded process.
The fix: Switched to Queue Mode — added Redis as the message broker, split the main instance (UI + webhook triggers) from dedicated stateless worker instances, and set EXECUTIONS_MODE=queue so execution load gets offloaded to workers.
Dev vs. Production, at a Glance
| Component | Dev / Small Instance | Production Setup |
|---|---|---|
| Database | SQLite | Dedicated PostgreSQL |
| Execution engine | Single-threaded main process | Multi-worker Queue Mode via Redis |
| Reverse proxy | Direct port exposure | Nginx/Traefik with automated TLS |
| Data retention | Indefinite (high disk usage) | Automated pruning enabled |
| Secrets | Auto-generated ephemeral key | Explicit N8N_ENCRYPTION_KEY |