Why Self-Host n8n + Langfuse
n8n is an open-source workflow automation engine. Langfuse is an LLM observability platform (tracing, evaluation, and prompt management). Combined with Docker, you get: n8n orchestrates LLM workflows → Langfuse records every node's input/output → you spot token drain or latency spikes.
I ran this on a 1-core 2GB VPS (Tencent Cloud Lighthouse, ~$4/month), Ubuntu 22.04 LTS. Single n8n fits fine. Add Langfuse v3 (needs PostgreSQL + Redis + Langfuse itself) and you're squeezed.
5 pitfalls covered (by frequency):
1. n8n container Running but returns 502 — n8n process crashed
2. n8n environment variables silently ignored in Docker — N8N_TIMEOUT_EXECUTION etc.
3. Langfuse v3 requires Redis/Valkey — new mandatory dependency
4. OOM crashes on 2GB VPS — swap + container memory limits
5. PostgreSQL connection pool exhaustion — max_connections not enough
n8n Container Running but Returns 502
**Symptom:** docker ps shows n8n as Up, but http://your-vps:5678 returns 502 Bad Gateway.
Root cause: The container is alive (PID 1 is tini/entrypoint.sh), but the n8n Node.js process inside crashed. Port 5678 isn't listening.
Debug steps:
# 1. Watch live container logs
docker logs n8n --tail 100 --follow
# 2. Check if n8n process is running inside container
docker exec n8n ps aux | grep n8n
# 3. No node process = n8n main process crashed
# Health check
docker exec n8n curl http://localhost:5678/healthz
Typical crash log:
Error: ENOENT: no such file or directory, open '/home/node/.n8n/config'
at Object.openSync (node:internal/filesystem:983:3)
at Object.readFileSync (node:internal/filesystem:1148:3)
Fix: Usually data directory permissions or failed volume mount:
# Fix data directory permissions
sudo chown -R 1000:1000 ./n8n_data
# Recreate volumes via Docker Compose
docker compose down -v
docker compose up -d
# Verify health
sleep 5 && docker exec n8n curl -s http://localhost:5678/healthz
If logs show memory-related crashes (JavaScript heap out of memory), you need to increase container memory limits or add swap.
n8n Environment Variables Ignored in Docker
**Symptom:** You set N8N_TIMEOUT_EXECUTION or EXECUTIONS_TIMEOUT in docker-compose.yml, but n8n shows "Undefined or empty".
**Root cause:** n8n in Docker reads environment variables at specific times and in specific ways. Some variables must exist in the host environment before container start, or must be explicitly passed in the environment section — not just in a .env file.
**Correct approach:** Define variables directly in the docker-compose.yml environment section, not just in the host's .env file:
version: '3.8'
services:
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=YOUR_STRONG_PASSWORD
# Key: execution timeout must be in environment section explicitly
- N8N_TIMEOUT_EXECUTION=600000 # 10 minutes
- EXECUTIONS_TIMEOUT=600000
- WEBHOOK_URL=https://your-domain.com/
- N8N_PROTOCOL=https
- N8N_PORT=5678
mem_limit: 1g
mem_reservation: 256m
volumes:
n8n_data:
Verify environment variables are loaded:
Create a Function node in n8n and run:
return {
timeout: process.env.N8N_TIMEOUT_EXECUTION,
execTimeout: process.env.EXECUTIONS_TIMEOUT
};
If it shows undefined, the variables aren't being passed correctly.
**Common mistake:** Writing variable names in env_file (N8N_TIMEOUT_EXECUTION=600000) without also defining them in the environment section — they only load from env_file when that mechanism is explicitly configured, not automatically.
Langfuse v3 Requires Redis/Valkey
Symptom: You followed the official Langfuse docs to deploy docker-compose.yml, but the container exits with:
Missing Redis/Valkey - Langfuse v3 requires Redis or Valkey for queuing events and caching data
Root cause: Langfuse v3 (stable released December 2024) changed architecture — Redis went from optional to mandatory, used for event queuing and caching.
Complete docker-compose.yml (n8n + Langfuse + Postgres + Redis):
version: '3.8'
services:
# PostgreSQL Database
postgres:
image: postgres:15-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=langfuse
- POSTGRES_PASSWORD=langfuse_password
- POSTGRES_DB=langfuse
volumes:
- postgres_data:/var/lib/postgresql/data
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U langfuse"]
interval: 10s
timeout: 5s
retries: 5
# Redis Message Queue (Langfuse v3 mandatory)
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
mem_limit: 384m
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Langfuse Main App
langfuse:
image: langfuse/langfuse:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://langfuse:langfuse_password@postgres:5432/langfuse
- REDIS_URL=redis://redis:6379
- NEXTAUTH_SECRET=your_nextauth_secret_here
- NEXTAUTH_URL=http://your-domain.com:3000
- SALT=your_salt_here
- TELEMETRY_ENABLED=false
ports:
- "3000:3000"
mem_limit: 1g
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/public/health"]
interval: 30s
timeout: 10s
retries: 3
# n8n Workflow Engine
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
depends_on:
- postgres
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=CHANGE_ME
- WEBHOOK_URL=https://your-domain.com/
- N8N_PROTOCOL=https
- N8N_PERSONAL_EXCLUSIONS=owner,admin,administrator
mem_limit: 768m
volumes:
postgres_data:
redis_data:
n8n_data:
New required environment variables in Langfuse v3:
- `DATABASE_URL` — PostgreSQL connection string (existed in v2 but now mandatory)
- `REDIS_URL` — Redis connection string (new in v3, was optional before)
- `SALT` — Password salting (new in v3, previously used NEXTAUTH_SECRET)
OOM Crashes on 2GB VPS
**Symptom:** Container runs fine for hours or days, then becomes unresponsive. docker ps shows Exited. dmesg shows oom-killer records.
Root cause: 2GB VPS running PostgreSQL + Redis + Langfuse + n8n sits right at the memory threshold. Any traffic spike (like concurrent workflow executions) triggers the OOM Killer.
Diagnostic commands:
# Check memory usage
free -h
# Check which processes OOM Killer terminated
grep -i oom /var/log/syslog
# or
dmesg | grep -i kill
# Check container memory usage
docker stats --no-stream
Fix (3-step):
Step 1: Add swap (simplest)
# Check if swap exists
swapon -s
# Create 2GB swap file (if none exists)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make permanent: add to fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Step 2: Set hard memory limits per container
In docker-compose.yml, add mem_limit to each service (shown in the complete config above):
- postgres: 512m
- redis: 384m
- langfuse: 1g
- n8n: 768m
Step 3: Tune PostgreSQL shared buffers
# Inside postgres container (or via POSTGRES env vars)
# Recommended: 25% of container memory
shared_buffers = 128MB
effective_cache_size = 256MB
work_mem = 16MB
Memory monitoring script (add to crontab):
# Check every 5 minutes, warn if memory <20%
*/5 * * * * free -m | awk '{if($3/$2 < 0.2) print "Low memory: "$3"M used of "$2"M"}' | logger -t mem-alert
PostgreSQL Connection Pool Exhaustion
**Symptom:** Langfuse or n8n suddenly throws FATAL: remaining connection slots are reserved for non-replication superuser connections, then all requests fail.
**Root cause:** PostgreSQL defaults to max_connections=100, but each Langfuse and n8n container can use dozens of connections from their internal pools. On a resource-constrained VPS, the default config exhausts quickly.
Fix:
Option A: Reduce max connections per container
# Langfuse side: limit Prisma connection pool
environment:
- DATABASE_URL=postgresql://langfuse:langfuse_password@postgres:5432/langfuse?connection_limit=5&pool_timeout=10
Option B: Lower PostgreSQL max_connections
-- Enter postgres container
docker exec -it postgres psql -U langfuse
-- Check current connections
SELECT count(*) FROM pg_stat_activity;
-- Change max connections (temporary, resets on restart)
ALTER SYSTEM SET max_connections = 50;
SELECT pg_reload_conf();
-- Make permanent: restart postgres container
docker restart postgres
Option C: Use PgBouncer connection pooler (recommended for production)
# Add pgbouncer service to docker-compose.yml
pgbouncer:
image: edoburu/pgbouncer:latest
restart: unless-stopped
depends_on:
- postgres
environment:
- POOL_MODE=transaction
- MAX_CLIENT_CONN=200
- DEFAULT_POOL_SIZE=20
- MIN_POOL_SIZE=5
- RESERVE_POOL_SIZE=5
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=langfuse
- DB_USER=langfuse
- DB_PASSWORD=langfuse_password
ports:
- "5433:5432"
Change Langfuse's DATABASE_URL to point to pgbouncer:
DATABASE_URL=postgresql://langfuse:langfuse_password@pgbouncer:5432/langfuse
Complete Deployment Verification Checklist
After deployment, verify each component in order:
# 1. Check all container status
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 2. Verify n8n health
curl -s http://localhost:5678/healthz
# Expected: {"status":"ok"}
# 3. Verify Langfuse health
curl -s http://localhost:3000/api/public/health
# Expected: {"status":"ok"} in JSON
# 4. Verify Redis
docker exec redis redis-cli ping
# Expected: PONG
# 5. Verify PostgreSQL connection
docker exec postgres pg_isready -U langfuse
# Expected: accepting connections
# 6. External access check (firewall)
# Ensure these ports are open on VPS firewall:
# 5678 (n8n), 3000 (Langfuse), 5432 (postgres is dangerous if exposed publicly, restrict source IP)
sudo ufw allow 5678/tcp
sudo ufw allow 3000/tcp
Pitfall Summary
| Problem | Root Cause | Fix |
|---|---|---|
| n8n 502 but container Running | n8n process crashed, container alive but service dead | Check `docker logs`, fix data dir permissions, add memory limits |
| Environment variables ignored | Not in environment section, or wrong variable name | Explicitly define in docker-compose.yml environment section |
| Langfuse v3 Redis required | v3 architecture change, Redis went from optional to mandatory | Add Redis service, update docker-compose.yml |
| 2GB VPS OOM | Total RAM insufficient, traffic spike triggers OOM Killer | Add swap, set mem_limit per container, tune PostgreSQL memory |
| PostgreSQL connection pool exhausted | max_connections default 100 not enough | Limit per-container connections or add PgBouncer |
Hardware recommendation: If budget allows, upgrade to a 2-core 4GB VPS (~$7/month) to avoid most memory issues. 2GB works for n8n alone or Langfuse alone, but running both requires the swap + memory limit combination.
👉 Want to experiment with AI coding tools without burning through tokens? MiniMax Token Plan is cost-effective for individual developers testing AI workflows: Get started
📌 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: