Zero-Trust n8n: Hardening with Cloudflare Tunnels, mTLS, and Isolated Ingress
Running self-hosted n8n in production exposes critical infrastructure risks if basic security boundaries are omitted. A standard setup with ports 80 and 443 wide open invites continuous credential brute-forcing against /login, automated vulnerability scans, and direct DDoS attacks against workflow webhook endpoints.
True production hardening follows a Zero-Trust architecture:
- Zero Open Inbound Ports: The host firewall drops all incoming traffic; connectivity to the outside world occurs strictly through outbound encrypted tunnels.
- Control Plane Isolation: Access to the n8n Editor UI and REST API is gated behind Single Sign-On (SSO) with Multi-Factor Authentication (MFA) or client-certificate authentication (mTLS).
- Public Webhook Segregation: Only specific webhook paths (
/webhook/*) remain externally accessible, protected by Edge WAF rules and rate limiters.
Prerequisite: Before hardening your ingress, ensure your core cluster is running securely in Queue Mode. Consult our blueprint on Deploying n8n in Queue Mode with Docker Composeand make sure your database is protected with Automated Encrypted S3 Backups.
Zero-Trust Network Topology Diagram
<!-- External Actors -->
<rect x="20" y="40" width="170" height="60" rx="8" fill="#1E293B" stroke="#A855F7" stroke-width="1.5"/>
<text x="105" y="67" fill="#F8FAFC" font-size="13" font-weight="600" text-anchor="middle">DevOps Engineer</text>
<text x="105" y="87" fill="#C084FC" font-size="11" text-anchor="middle">Browser / mTLS cert</text>
<rect x="20" y="240" width="170" height="60" rx="8" fill="#1E293B" stroke="#38BDF8" stroke-width="1.5"/>
<text x="105" y="267" fill="#F8FAFC" font-size="13" font-weight="600" text-anchor="middle">Third-Party SaaS</text>
<text x="105" y="287" fill="#93C5FD" font-size="11" text-anchor="middle">Stripe / HubSpot Webhook</text>
<!-- Path from Dev to Edge -->
<line x1="190" y1="70" x2="250" y2="70" stroke="#A855F7" stroke-width="2" marker-end="url(#arr-purple)"/>
<!-- Path from SaaS to Edge -->
<line x1="190" y1="270" x2="250" y2="270" stroke="#38BDF8" stroke-width="2" marker-end="url(#arr-blue)"/>
<!-- Edge Security Layer -->
<rect x="250" y="30" width="220" height="310" rx="10" fill="#0D1527" stroke="#F59E0B" stroke-width="1.5"/>
<text x="360" y="55" fill="#FBBF24" font-size="12" font-weight="700" text-anchor="middle">CLOUDFLARE ZERO-TRUST EDGE</text>
<rect x="270" y="75" width="180" height="70" rx="6" fill="#1E293B" stroke="#A855F7" stroke-width="1"/>
<text x="360" y="102" fill="#FFFFFF" font-size="12" font-weight="600" text-anchor="middle">Cloudflare Access</text>
<text x="360" y="122" fill="#94A3B8" font-size="10" text-anchor="middle">mTLS & IdP Authentication</text>
<rect x="270" y="225" width="180" height="70" rx="6" fill="#1E293B" stroke="#38BDF8" stroke-width="1"/>
<text x="360" y="252" fill="#FFFFFF" font-size="12" font-weight="600" text-anchor="middle">Cloudflare WAF</text>
<text x="360" y="272" fill="#94A3B8" font-size="10" text-anchor="middle">Rate Limiting & DDoSPass</text>
<!-- Tunnel Connection -->
<line x1="470" y1="185" x2="540" y2="185" stroke="#10B981" stroke-width="2.5" stroke-dasharray="5,3" marker-end="url(#arr-green)"/>
<text x="505" y="175" fill="#34D399" font-size="10" font-weight="600" text-anchor="middle">cloudflared</text>
<!-- VPS Internal Network -->
<rect x="540" y="30" width="260" height="310" rx="10" fill="#0F172A" stroke="#1E293B" stroke-width="2"/>
<text x="670" y="55" fill="#94A3B8" font-size="12" font-weight="700" text-anchor="middle">VPS (UFW: ALL INBOUND BLOCKED)</text>
<rect x="565" y="80" width="210" height="65" rx="6" fill="#1E293B" stroke="#334155" stroke-width="1"/>
<text x="670" y="107" fill="#F8FAFC" font-size="12" font-weight="600" text-anchor="middle">n8n UI / Editor</text>
<text x="670" y="127" fill="#94A3B8" font-size="10" text-anchor="middle">localhost:5678</text>
<rect x="565" y="230" width="210" height="65" rx="6" fill="#1E293B" stroke="#10B981" stroke-width="1"/>
<text x="670" y="257" fill="#34D399" font-size="12" font-weight="600" text-anchor="middle">n8n Webhook Workers</text>
<text x="670" y="277" fill="#94A3B8" font-size="10" text-anchor="middle">Queue Mode Processors</text>
Step 1: Lock Down the Host Firewall (UFW)
Because Cloudflare Tunnels establish an outbound-only connection over port 7844 (UDP/QUIC or TCP) to the Cloudflare edge, you do not need to open port 80 (HTTP) or 443 (HTTPS) to the public internet on your host instance.
Run the following commands on your Ubuntu/Debian VPS:
# Allow SSH first so you don't lock yourself out!
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH Host Access'
sudo ufw enable
Verify your firewall status:
sudo ufw status verbose
At this stage, your host will actively drop all external probes on ports 80, 443, 5678, and 5432.
Step 2: Deploy the Cloudflared Daemon via Docker
Instead of installing packages directly on the host, run the tunnel as a lightweight container inside your existing Docker Compose network.
Add the cloudflared service directly into your docker-compose.yml:
services:
# --- Existing n8n, postgres, and redis services here ---
cloudflared:
image: cloudflare/cloudflared:latest
container_name: n8n-cloudflare-tunnel
restart: always
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
command: tunnel --no-autoupdate run
networks:
- n8n_internal_net
networks:
n8n_internal_net:
external: true
Get your TUNNEL_TOKEN from the Cloudflare One Dashboard under Networks > Tunnels > Create a Tunnel.
Step 3: Splitting Ingress Rules (UI vs. Webhooks)
Configure Public Hostname routing within your Cloudflare Tunnel settings to isolate administrative paths from public webhook receivers:
1. Ingress Rule for Webhooks (Public)
- Subdomain:
webhooks.yourdomain.com - Path:
/webhook/*and/webhook-test/* - Service:
HTTP://n8n-main:5678 - Access Policy: None (open for external SaaS webhooks, but protected via Cloudflare WAF rate-limiting).
2. Ingress Rule for the Editor UI (Zero-Trust Gated)
- Subdomain:
n8n.yourdomain.com - Service:
HTTP://n8n-main:5678 - Access Policy: Protected with Cloudflare Access.
Step 4: Gating the Editor Behind Cloudflare Access (IdP or mTLS)
To access the n8n Canvas UI, configure an Access Application in Cloudflare One:
- Navigate to Access > Applications > Add an application (Self-hosted).
- Set the application domain to
n8n.yourdomain.com. - Under Policies, define an access rule:
- Action: Allow
- Include Rule: Everyone with email addresses ending in
@yourcompany.comauthenticated via Google Workspace, Okta, or GitHub.
- (Optional – Maximum Paranoia) Enable mTLS (Mutual TLS): Under Access > Service Auth, generate a client certificate and install it on your work machines. Cloudflare will drop any browser connection immediately at the TLS handshake level if the cryptographic client certificate is missing.
Step 5: Webhook Hardening with Edge WAF Rules
For endpoints that accept external webhooks (such as /webhook/crm-sync-inbound), prevent flood-based compute exhaustion by defining edge WAF rules in the Cloudflare Dashboard:
- Rate Limit Policy: Match requests targeting URI path containing
/webhook/. Cap throughput to100 requests per 10 secondsper IP address. Action: Block (429). - Payload Size Ceiling: In WAF > Web Application Firewall, set request body inspection limits to drop payloads exceeding
10 MB. - HMAC Signature Check: Whenever possible, use pre-shared secrets and verify headers (
X-HubSpot-Signature-v3orStripe-Signature) inside your n8n workflow code using our Resilient Webhook DLQ Blueprint.
Frequently Asked Questions
Does routing webhooks through Cloudflare Tunnels add latency?
Cloudflare’s global Anycast edge typically adds fewer than 5–15 milliseconds of round-trip latency. In return, your VPS IP remains completely masked, mitigating direct-to-IP DDoS and port-scanning activity.
What happens if the cloudflared container crashes?
Docker’s restart: always flag ensures automatic recovery within seconds. In high-availability enterprise environments, deploy two redundant cloudflared replicas on separate nodes sharing the same tunnel token.
Do I still need n8n user authentication if Cloudflare Access is active?
Yes. Implement defense-in-depth: keep standard n8n user accounts and MFA active. If an internal network misconfiguration ever bypasses the edge layer, the application-level authentication barrier remains intact.
3 comentarios