FlowKit

n8n over HTTPS with Cloudflare Tunnel: no open port, no certificate

Published 3 August 2026 · 10 min read

To put n8n behind HTTPS on your own domain, the two classic routes are a reverse proxy with automatic certificates — Traefik or Caddy — and the more hands-on Nginx + Certbot setup. Both share a silent prerequisite: a machine whose ports 80 and 443 are reachable from the Internet. That's true of a standard VPS, but not of a server sitting behind a home router, a mini-PC in a closet, a family NAS, or a corporate network whose firewall won't open up for you. And even on a VPS, you can legitimately prefer not to expose port 443 to the entire world.

Cloudflare Tunnel is the third way: a small connector, cloudflared, runs next to n8n and establishes an outbound connection to the Cloudflare network. Inbound traffic takes the reverse path — Cloudflare, the tunnel, your container — without a single inbound port ever being opened. TLS, certificate, renewal: all handled on Cloudflare's side. This guide covers the full Docker setup, the n8n variables without which your webhooks will point to localhost, protecting the editor with Cloudflare Access, and the limits of the model you should accept with eyes open.

Why "no open port" is a real argument

People routinely underestimate how fast an open port on the Internet gets discovered. It's not a matter of staying discreet or getting lucky: the foundational study by Durumeric, Wustrow and Halderman presented at USENIX Security 2013, ZMap: Fast Internet-wide scanning and its security applications (see on Google Scholar), showed that a single well-equipped machine can scan the entire IPv4 address space in under an hour. In other words, any exposed service — your port 443, or port 5678 if you left n8n directly reachable — is found and probed continuously by automated scanners within minutes or hours of going online, not months.

A well-configured reverse proxy handles that reality just fine; it's not a reason to flee Traefik or Nginx. But a machine that exposes no inbound port at all presents a direct attack surface of zero to those scanners: there is literally nothing to probe. That's the Cloudflare Tunnel model, and it's also what makes it usable where opening a port is impossible — behind a carrier-grade NAT, on a locked-down corporate network, or at home without touching the router's configuration.

How it works, and what you need

The principle fits in three moves:

  1. The cloudflared container starts and opens persistent outbound connections to the Cloudflare network (the same way any HTTP client leaves your network — no firewall rule or port forwarding to create).
  2. Cloudflare maps your tunnel to a public hostname, say n8n.yourdomain.com, with a DNS record created automatically.
  3. When someone visits https://n8n.yourdomain.com, the request lands on the Cloudflare network, which terminates TLS and sends it back down through the tunnel to the service you designated — here, the n8n container on its port 5678.

The single prerequisite: a domain whose DNS is managed by Cloudflare. The free plan is more than enough — Cloudflare Tunnel is included in it, with tunnel counts and traffic allowances that are very comfortable at SMB scale. If your domain lives at OVH, Gandi or elsewhere, you don't need to transfer it: just add it to a Cloudflare account and point its nameservers (NS) to Cloudflare's at your registrar.

On the machine side, this guide assumes an n8n instance running in Docker — our Docker installation guide lays that foundation if it's not there yet. The machine can be a regular VPS (see our VPS selection guide), but the tunnel's specific appeal is precisely that it can also be anything else: a €150 mini-PC, an old laptop, a Raspberry Pi behind the living-room router.

Creating the tunnel in the Zero Trust dashboard

The recommended method goes through the dashboard, which manages configuration remotely (the command-line alternative with a local config.yml file exists, but the dashboard is simpler and covers this use case fully):

  1. Log into the Cloudflare Zero Trust dashboard (reachable from your Cloudflare account, Zero Trust section — the initial setup asks you to pick a team name and a plan; the free plan works).
  2. Under Networks → Tunnels, create a tunnel of type Cloudflared and give it a name (n8n-home, for instance).
  3. Cloudflare then displays a token — a long string starting with eyJ. It's the only piece of information the cloudflared container will need: copy it, that's your TUNNEL_TOKEN. Treat it like a password.
  4. In the tunnel's Public Hostname tab, add an entry: subdomain n8n, domain yourdomain.com, service type HTTP and URL n8n:5678 — that is, the Docker service name of your n8n container, followed by its internal port. Cloudflare creates the matching DNS record automatically.

That last point deserves emphasis: the service URL is resolved from inside the cloudflared container, not from your machine. localhost:5678 would point to the cloudflared container itself and lead nowhere. It's the same trap as database credentials in Docker: containers on the same network address each other by service name.

The complete docker-compose

Two services on the same Docker network: n8n, and cloudflared, which only needs its token.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - N8N_HOST=n8n.yourdomain.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.yourdomain.com/
      - N8N_EDITOR_BASE_URL=https://n8n.yourdomain.com/
      - GENERIC_TIMEZONE=Europe/Paris
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n-net

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    networks:
      - n8n-net

volumes:
  n8n_data:

networks:
  n8n-net:

Three details that matter:

  • No ports: anywhere. That's the whole point: n8n isn't published on the host machine, and cloudflared doesn't listen for anything — it only dials out. If your existing compose file contains a ports: - "5678:5678" on n8n, remove it: leaving it in would expose the instance over plain HTTP alongside the tunnel, cancelling the benefit.
  • The token lives in a .env file next to the compose file (TUNNEL_TOKEN=eyJ...), not hardcoded in the YAML — especially if that file ever lands in a Git repository.
  • The shared network n8n-net is what lets cloudflared resolve the name n8n. In a single compose file like this one, the default network would suffice, but declaring it explicitly avoids surprises if the services are ever split across several files.

One docker compose up -d later, the tunnel shows as "Healthy" in the Zero Trust dashboard, and https://n8n.yourdomain.com serves your instance over HTTPS. No certificate was generated on your side, and there will never be one to renew.

The n8n variables that make or break webhooks

The tunnel carries the traffic, but n8n has to know the public address it's being served under — otherwise it builds its URLs from what it sees locally, which is nothing useful. Three variables are essential, already present in the compose file above:

  • N8N_HOST=n8n.yourdomain.com — the instance's public hostname.
  • WEBHOOK_URL=https://n8n.yourdomain.com/ — the base for every generated webhook URL. Without it, the editor shows URLs like http://localhost:5678/webhook/... that Stripe, Typeform or any external service will obviously never be able to call. It's the number-one symptom of an incomplete tunnel setup.
  • N8N_EDITOR_BASE_URL=https://n8n.yourdomain.com/ — the editor's base URL, used notably in links the instance generates (OAuth credential callbacks, links in emails).

After changing these variables, a docker compose up -d recreates the container and the URLs shown in Webhook nodes switch to the public domain. Webhook behavior through the tunnel is then strictly identical to behind a reverse proxy — including when it comes to protecting them: the best practices from our webhook security guide (unguessable paths, signature verification, secret header) apply unchanged.

Security bonus: Cloudflare Access in front of the editor

Your instance is now on HTTPS, but its editing interface remains reachable by anyone who knows the URL — protected only by n8n's own login page. Cloudflare Access, included in the same Zero Trust offering, lets you add an authentication layer before the request even reaches n8n, directly on the Cloudflare network.

The principle: in the Zero Trust dashboard, you create an Access application covering n8n.yourdomain.com, with a policy defining who gets through — for instance "my team's email addresses, verified by a one-time code sent by email". Any unauthenticated visitor hits a Cloudflare login page before seeing anything of n8n at all. For an instance whose editor was never meant to be public, it's a remarkably effective barrier for a minimal setup cost.

The subtlety is that webhooks must stay public: Stripe or Typeform will never know how to answer a Cloudflare authentication challenge. The solution is to create a second, more specific Access application covering n8n.yourdomain.com/webhook/* (and /webhook-test/* if you test from outside), with a bypass policy that lets everyone through. Access applies the most specific rule: the editor requires authentication, the webhook paths don't. Securing the webhooks themselves then relies on the classic mechanisms — signatures, shared secrets — described in the dedicated guide.

The model's honest limits

Cloudflare Tunnel is an excellent answer to a specific problem, not a universally superior solution. Three limits to accept knowingly:

Total dependence on Cloudflare. A Cloudflare network outage, an incident on your account, a suspension: your instance becomes unreachable from outside, with no plan B possible since no port is open. Scheduled workflows keep running locally, but inbound webhooks are cut off for the duration of the incident. With a reverse proxy on a VPS, you depend "only" on your hosting provider; here, you're adding a link to the chain.

All traffic transits through Cloudflare. TLS is terminated on the Cloudflare network before heading back down the tunnel: Cloudflare technically sees the traffic in the clear, as any CDN in proxy mode would. For most SMB uses, that's a mundane, accepted trade-off (a large part of the web already works this way); for highly sensitive data or strict compliance requirements, it's a question to settle explicitly, not to discover after the fact.

Long HTTP requests. Cloudflare enforces a maximum duration on HTTP requests passing through it. A webhook triggering a workflow that responds within a few seconds will never notice; a workflow that keeps the caller waiting through a long process (AI agent, document generation, heavy scraping) risks having the connection cut by Cloudflare before n8n answers. The fix isn't to try to stretch the limit, but to switch to an asynchronous pattern — respond immediately, then deliver the result via callback — as detailed in our guide on webhook timeouts with AI agents. It's the right architecture anyway, tunnel or not.

If none of these limits concerns you and you have a VPS with openable ports, the Traefik/Caddy solutions remain a perfectly defensible choice, with no intermediary. The tunnel wins as soon as opening a port is impossible, forbidden, or simply unwanted.

Common pitfalls

  • Pointing the public hostname at localhost:5678 instead of the Docker service name n8n:5678: from inside the cloudflared container, localhost is cloudflared itself. The tunnel shows "Healthy" but returns a connection error.
  • Forgetting WEBHOOK_URL: the editor works perfectly through the tunnel, but every webhook URL it displays points to localhost — and nothing external can call them.
  • Leaving a ports: block on the n8n service in the compose file: the instance then stays exposed over direct HTTP on the machine, right next to the tunnel, ruining the "no open port" argument.
  • Hardcoding the token in docker-compose.yml and then pushing the file to Git: the token grants control of the tunnel; it belongs in a .env file ignored by Git.
  • Protecting the whole domain with Cloudflare Access without a bypass policy on /webhook/*: every external webhook starts failing silently with redirects to the Cloudflare login page.
  • Keeping the caller waiting through a long process across the tunnel: Cloudflare will cut the connection before the end. Respond immediately, process asynchronously.
  • Mixing up the two configuration modes: if you follow a tutorial based on a local config.yml file while your tunnel is dashboard-managed (token), the file's settings are ignored. Pick one mode and stick to it — the dashboard, for simplicity.

In summary

Cloudflare Tunnel completes the trio of ways to serve n8n over HTTPS: Traefik/Caddy for full automation on an open VPS, Nginx for total control, and the tunnel for every case where opening a port is impossible or unwanted — a server behind a home router, a mini-PC at home, a corporate firewall, or a plain preference for a zero inbound attack surface. One cloudflared container, one token, one public hostname pointing at n8n:5678, the three URL variables on n8n's side, and the instance is in HTTPS production with no certificate to manage and no port to watch. And once the instance is publicly reachable over HTTPS, it can serve far more than technical webhooks: the RAG Assistant Pack (€119) turns it into the foundation of a complete document chatbot — your documents, an indexing pipeline, and a public chat endpoint that this tunnel exposes cleanly to the world.

FAQ

Frequently asked questions

Do I still need a reverse proxy (Traefik, Caddy, Nginx) with Cloudflare Tunnel?

No. The cloudflared connector talks directly to the n8n container over plain HTTP on the internal Docker network (http://n8n:5678), and Cloudflare terminates TLS on its side. No certificate to generate, no port 80 or 443 to open on the machine. A local reverse proxy only becomes useful again if you host several services behind the same tunnel and want to centralize local rules — but for a single n8n instance, cloudflared is enough.

Do n8n webhooks work through a Cloudflare Tunnel?

Yes, exactly as they would behind a classic reverse proxy: a request to https://n8n.yourdomain.com/webhook/... crosses the Cloudflare network, comes back down through the tunnel and reaches the n8n container. The non-negotiable condition is having set WEBHOOK_URL=https://n8n.yourdomain.com/ in n8n's environment — otherwise the URLs shown in the editor point to localhost and no external service will ever be able to call them. Just watch out for long synchronous executions: Cloudflare enforces a maximum duration on HTTP requests, so respond fast and process asynchronously.

What happens if Cloudflare goes down or suspends my account?

Your instance becomes unreachable from the outside: all inbound traffic goes through the Cloudflare network, and there is no fallback path since no port is open on the machine. The instance keeps running locally (scheduled workflows execute normally), but inbound webhooks and editor access from the Internet are cut off. That's the trade-off you accept in exchange for the simplicity — for critical use, keep a local or VPN backup path to the machine.

Bundle FlowKit Complet

€269