Documents the N8N_PROXY_HOPS env var needed for n8n behind Caddy/Nginx when N8N_TRUST_PROXY alone is insufficient in newer versions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2.4 KiB
n8n Behind Reverse Proxy: X-Forwarded-For Trust Fix
The Problem
When running n8n behind a reverse proxy (Caddy, Nginx, Traefik), the logs fill with:
ValidationError: The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default).
This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.
This means n8n's Express rate limiter sees every request as coming from the proxy's internal IP, not the real client. Rate limiting and audit logging both break.
Why N8N_TRUST_PROXY=true Isn't Enough
Older n8n versions accepted N8N_TRUST_PROXY=true to trust proxy headers. Newer versions (1.x+) use Express's trust proxy setting, which requires knowing how many proxy hops to trust. Without N8N_PROXY_HOPS, Express ignores the X-Forwarded-For header entirely even if N8N_TRUST_PROXY=true is set.
The Fix
Add N8N_PROXY_HOPS=1 to your n8n environment:
Docker Compose
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
environment:
- N8N_HOST=n8n.example.com
- N8N_PROTOCOL=https
- N8N_TRUST_PROXY=true
- N8N_PROXY_HOPS=1 # <-- Add this
Set N8N_PROXY_HOPS to the number of reverse proxies between the client and n8n:
- 1 — single proxy (Caddy/Nginx directly in front of n8n)
- 2 — two proxies (e.g., Cloudflare → Caddy → n8n)
Recreate the Container
cd /opt/n8n # or wherever your compose file lives
docker compose down
docker compose up -d
If you get a container name conflict:
docker rm -f n8n-n8n-1
docker compose up -d
Verifying the Fix
Check the logs after restart:
docker logs --since 5m n8n-n8n-1 2>&1 | grep -i "forwarded\|proxy\|ValidationError"
If the fix worked, there should be zero ValidationError lines. A clean startup looks like:
n8n ready on ::, port 5678
Version: 2.14.2
Editor is now accessible via:
https://n8n.example.com
Key Notes
- Keep
N8N_TRUST_PROXY=truealongsideN8N_PROXY_HOPS— both are needed. - The
mount of type volume should not define bind optionwarning from Docker Compose when using:z(SELinux) volume labels is cosmetic and can be ignored. - If n8n reports "Last session crashed" after a
docker rm -frecreation, this is expected — the old container was force-killed, so n8n sees it as a crash. It recovers automatically.