Automation by Vitale Mazo

n8n Behind Cloudflare + OPNsense HAProxy (Unraid Backend)

Step-by-step topology for exposing n8n through Cloudflare, OPNsense HAProxy, and Unraid with the right headers, WebSocket support, and TLS expectations.

n8n Behind Cloudflare + OPNsense HAProxy (Unraid Backend)
#n8n #Cloudflare #OPNsense #HAProxy #Self-Hosting

n8n behind Cloudflare + OPNsense HAProxy (Unraid backend)

Expose n8n at https://n8n.example.com through Cloudflare → OPNsense/HAProxy → Unraid (Docker) with correct headers and WebSocket support. Avoids common pitfalls that cause CSRF “Origin header does NOT match…” and HTTP 503.


Topology

n8n Network Topology Diagram

Figure 1: n8n Network Architecture - Cloudflare → OPNsense → Unraid

Browser ──HTTPS──► Cloudflare (proxy: orange cloud)


      OPNsense (HAProxy, TLS offload @ :443)


   Unraid (Docker) → n8n container (HTTP :5678)

🔧 Key Components

  • Cloudflare: DNS proxy with DDoS protection and SSL termination
  • OPNsense + HAProxy: Load balancer with TLS offloading
  • Unraid Docker: Containerized n8n instance

Prerequisites

  • Cloudflare DNS: n8n.example.com → WAN IP of OPNsense (proxied, orange cloud).
  • OPNsense with HAProxy plugin.
  • Unraid Docker n8n container reachable on LAN (e.g., 10.0.3.129:5678).
  • Valid TLS cert on OPNsense (e.g., *.example.com via ACME client).

1) n8n container (Unraid) — required environment variables

Set these on the n8n container and recreate/apply:

  • N8N_HOST = n8n.example.com
  • N8N_PORT = 443
  • N8N_PROTOCOL = https
  • N8N_EDITOR_BASE_URL = https://n8n.example.com/
  • WEBHOOK_URL = https://n8n.example.com/
  • N8N_SECURE_COOKIE = true

Optional (noise reduction):

  • N8N_DIAGNOSTICS_ENABLED = false
  • N8N_DISABLE_VERSION_NOTIFICATIONS = true

Verify inside the running container:

docker exec -it n8n env | grep -E 'N8N_|WEBHOOK_URL'

If you ever saw “Expected: …” in logs, once envs are correct do this once:

docker stop n8n
rm -f /mnt/user/appdata/n8n/config
docker start n8n

Then hard-refresh your browser (Ctrl+F5 / Cmd+Shift+R).


2) OPNsense HAProxy — Backend (to n8n)

Real Server

  • Name: n8n_server
  • IP/FQDN: 10.0.3.129
  • Port: 5678
  • SSL: OFF (backend is plain HTTP)
  • Mode: active

Backend Pool

  • Name: n8n_backend
  • Mode: HTTP
  • Servers: n8n_server
  • Health check: HTTP GET /, expect 200–399
  • HTTP(S) settings: enable X-Forwarded-For
  • Persistence: None

Sanity from LAN:

curl -sS http://10.0.3.129:5678/ | head -n 5

3) OPNsense HAProxy — Conditions

Create in Rules & Checks → Conditions:

  • host_n8nHost matchesn8n.example.com
  • is_websocket_upgradeHTTP request header matches → Header Upgrade equals websocket (case-insensitive)

4) OPNsense HAProxy — Rules

Create in Rules & Checks → Rules:

  • set_xfp_httpshttp-request header set X-Forwarded-Proto = https
  • set_xfh_hosthttp-request header set X-Forwarded-Host %[req.hdr(host)]
  • ws_upgrade (condition is_websocket_upgrade) → http-request header set Upgrade websocket
  • ws_connection_upgrade (condition is_websocket_upgrade) → http-request header set Connection upgrade
  • route_to_n8n (condition host_n8n) → Use specified Backend Pooln8n_backend
  • (Port 80 only) redirect_to_https_allhttp-request redirect https://%[req.hdr(host)]%[req.uri] (code 301)

5) OPNsense HAProxy — Public Services (listeners)

Keep only one listener bound to 0.0.0.0:443. Disable duplicates to avoid conflicts.

Port 80 — redirect only

  • Name: public_facing_pool_80
  • Listen: 0.0.0.0:80
  • Default Backend: None
  • Rules: redirect_to_https_all (only this)
  • Basic Auth / Prometheus: Off

Port 443 — TLS offload + routing

  • Name: public_facing_pool_443
  • Listen: 0.0.0.0:443
  • SSL offloading: On, certificate *.example.com
  • Client Certificate Auth: Off (unless you require mTLS)
  • Basic Authentication: Off (unless intentional)
  • Rules attached:
    • set_xfp_https
    • set_xfh_host
    • ws_upgrade
    • ws_connection_upgrade
    • route_to_n8n (Host → n8n_backend)

Debug tip: If you get a 503, temporarily set Default Backend = n8n_backend on 443 to prove routing, then switch back to None once host routing works.


6) Cloudflare

  • DNS: n8n.example.com → WAN IP of OPNsense (proxied).
  • SSL/TLS mode: Full (strict) (not Flexible).
  • WebSockets: supported automatically over 443.

7) Verification

# 80 should redirect to 443
curl -I http://n8n.example.com
# → HTTP/1.1 301 Moved Permanently … Location: https://n8n.example.com/...

# 443 should succeed (200 / login page), not 503
curl -I https://n8n.example.com

# Backend from LAN
curl -sS http://10.0.3.129:5678/ | head -n 5

8) Troubleshooting

  • 503 on HTTPS

    • Multiple 443 listeners on 0.0.0.0:443 (disable extras).
    • Backend wrong/unreachable.
    • Missing host-route rule on 443.
  • “Origin header does NOT match the expected origin”

    • Missing/misspelled n8n env vars (verify N8N_* and WEBHOOK_URL).
    • Tiny settings file overriding envs → remove once (see step 1).
    • Browser cache: hard-refresh / incognito.
  • WebSocket issues

    • Missing HAProxy rules to set Upgrade: websocket and Connection: upgrade when Upgrade: websocket.

9) Optional hardening

  • HSTS on 443: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • Add rate limiting/stick tables as needed.

10) Rule quick reference

  • redirect_to_https_allhttp-request redirect https://%[req.hdr(host)]%[req.uri] (301)
  • set_xfp_httpshttp-request header set X-Forwarded-Proto https
  • set_xfh_hosthttp-request header set X-Forwarded-Host %[req.hdr(host)]
  • ws_upgrade (if Upgrade: websocket) → http-request header set Upgrade websocket
  • ws_connection_upgrade (if Upgrade: websocket) → http-request header set Connection upgrade
  • route_to_n8n (if Host n8n.example.com) → Use backend: n8n_backend

About the Author

Vitale Mazo is a Senior Cloud Engineer with 19+ years of experience in enterprise IT, specializing in cloud native technologies and multi-cloud infrastructure design.