- Fixed 4 broken markdown links (bad relative paths in See Also sections) - Corrected n8n port binding to 127.0.0.1:5678 (matches actual deployment) - Updated SnapRAID article with actual majorhome paths (/majorRAID, disk1-3) - Converted 67 Obsidian wikilinks to relative markdown links or plain text - Added YAML frontmatter to 35 articles missing it entirely - Completed frontmatter on 8 articles with missing fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
2.6 KiB
Markdown
85 lines
2.6 KiB
Markdown
---
|
|
title: "n8n Behind Reverse Proxy: X-Forwarded-For Trust Fix"
|
|
domain: troubleshooting
|
|
category: docker
|
|
tags: [n8n, caddy, reverse-proxy, docker, express]
|
|
status: published
|
|
created: 2026-04-02
|
|
updated: 2026-04-02
|
|
---
|
|
# 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
cd /opt/n8n # or wherever your compose file lives
|
|
docker compose down
|
|
docker compose up -d
|
|
```
|
|
|
|
If you get a container name conflict:
|
|
|
|
```bash
|
|
docker rm -f n8n-n8n-1
|
|
docker compose up -d
|
|
```
|
|
|
|
## Verifying the Fix
|
|
|
|
Check the logs after restart:
|
|
|
|
```bash
|
|
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=true` alongside `N8N_PROXY_HOPS` — both are needed.
|
|
- The `mount of type volume should not define bind option` warning 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 -f` recreation, this is expected — the old container was force-killed, so n8n sees it as a crash. It recovers automatically.
|