🔐 How I Secured My n8n Instance with Cloudflare Zero Trust (No Open Ports, No Public IP) further edits to come

Hey everyone,

I wanted to share how I’ve locked down my self-hosted n8n instance using Cloudflare Tunnel and Zero Trust access control. This setup is what many enterprise companies use, but it’s totally achievable even for solo devs or small teams. And yes — it’s free for most use cases.

My instance, if you visit, you’ll see a Cloudflare Access login screen first — that’s not just for show. It’s part of a much stronger security model.

n8n can connect to databases, run scripts, post to APIs — it’s incredibly powerful. But out of the box, many n8n instances are:

  • Exposed via a public IP address

  • Protected only by a basic password

  • That wasn’t good enough for me.

:shield: What I Set Up
Here’s the stack I’m now using to protect my instance:

:white_check_mark: Cloudflare Tunnel
The server runs a lightweight cloudflared daemon that creates a secure outbound tunnel to Cloudflare.

This means no open ports, no exposed IPs, and no way for attackers to reach the app directly.

:white_check_mark: Cloudflare Access (Zero Trust)
Before you even reach the login screen for n8n, you’re stopped by a Cloudflare auth gate.

Only approved emails or users can get through.

I currently allow only specific addresses (not just open domains like @gmail.com).


Security at mind! * My origin IP is completely hidden — no direct access possible
  • Cloudflare’s global edge provides DDoS protection and WAF

  • I can control who gets access, from where, and for how long

  • I get full audit logs of who accessed the app and when

  • This is the same security architecture used by financial and betting tech firms. It’s enterprise-grade Zero Trust — but free and easy to set up.



High-Level Setup Guide Enable Proxied DNS (orange cloud) in Cloudflare for your domain/subdomain.

*add domain to cloudflare, remember to point domain from provider to cloudflare.

You can also add ure encryption here for ssl.

Open ZeroTrust

Network Tunnels

Click CreateNew Tunnel:

Name tunnel and go to this page next


Save this token, for your docker .yml if using docker or if running command see details here. Just copy and paste to see full token. go next

Once you have done this, on the route tunnel page, enter like so, remember because cloudflared is running inside your network (mines inside dockers, with n8n, so I just call the (Docker’s built-in DNS system that automatically resolves container names to their IPs)

Tunnel should be created, now I use docker compose so I just spin up this with my n8n instance

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token tokenFromEarlierStep
    restart: always
    depends_on:
      - n8n

with

      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - N8N_PROTOCOL=https

You can run the command inside n8n if you wish, as earlier step shows, but this is sufficient for me.

Done

  • Install cloudflared on your server.
  • Create a tunnel



**SEPERATE EXTRA PROTECTION** That should be everything on cloudflare side for the tunnel. we still need to setup access rules and application if needed.

Open Applications

Click add, and select SelfHosted

Pick name, and click + on public hostname


On the same page, you have access policies, select Create New Policy


Add the security you want, IPs, Locations, Users etc

Then go back, and add the security policy.

You can edit

  • Experience settings (optional)
  • Advanced settings (optional)
    Also but defaults are okay, and finish creating the application.

You can then test, I used US but am in UK, so I get this now when access.

Done

  • Set up Cloudflare Access rules
  • Go to Zero Trust > Access > Applications

Happy to Help
If you’re self-hosting n8n and want to level up your security without adding tons of complexity, I highly recommend trying this. Feel free to ask questions or drop your setup — I’m happy to help others get this working.

Cheers!
(and yes — even the big guys like Fortune 500s use this stack, with more fine-grained polices.)

4 Likes

Great post! Thanks for sharing @King_Samuel_David

I’m very new to self-hosting in general so apologies in advance if I don’t provide enough information.

Most of the directions I followed made sense and I believe I followed them correctly (definitely not or I wouldn’t be here.)
The only step that confused me a bit was entering the Service URL on the Tunnels page in Cloudflare. in your screenshot you have “n8n-1:5678”, and that is what I entered, not sure if I should have entered something else.

Important Notes:

  • I’m using ProxMox to Run Docker in an Ubuntu LXC. (As I said I’m very new to all of this so its possible my issues lie with proxmox)

  • Here is what I used for my docker compose file (I forgot to copy it, but the cmd line gave no errors and everything seemingly installed correctly.):

    • services:
      traefik:
      image: “traefik”
      restart: always
      command:
      • “–api.insecure=true”
      • “–providers.docker=true”
      • “–providers.docker.exposedbydefault=false”
      • “–entrypoints.web.address=:80”
      • “–entrypoints.web.http.redirections.entryPoint.to=websecure”
      • “–entrypoints.web.http.redirections.entrypoint.scheme=https”
      • “–entrypoints.websecure.address=:443”
      • “–certificatesresolvers.mytlschallenge.acme.tlschallenge=true”
      • “–certificatesresolvers.mytlschallenge.acme.email=I ENTERED MY EMAIL HERE
      • “–certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json”
        ports:
      • “80:80”
      • “443:443”
        volumes:
      • traefik_data:/letsencrypt
      • /var/run/docker.sock:/var/run/docker.sock:ro

    n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
    - “127.0.0.1:5678:5678”
    labels:
    - traefik.enable=true
    - traefik.http.routers.n8n.rule=Host(***I ENTERED MY SUBDOMAIN.DOMAIN HERE***)
    - traefik.http.routers.n8n.tls=true
    - traefik.http.routers.n8n.entrypoints=web,websecure
    - traefik.http.routers.n8n.tls.certresolver=mytlschallenge
    - traefik.http.middlewares.n8n.headers.SSLRedirect=true
    - traefik.http.middlewares.n8n.headers.STSSeconds=315360000
    - traefik.http.middlewares.n8n.headers.browserXSSFilter=true
    - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
    - traefik.http.middlewares.n8n.headers.forceSTSHeader=true
    - traefik.http.middlewares.n8n.headers.SSLHost=I ENTERED MY DOMAIN HERE
    - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
    - traefik.http.middlewares.n8n.headers.STSPreload=true
    - traefik.http.routers.n8n.middlewares=n8n@docker
    environment:
    - N8N_HOST=I ENTERED MY SUBDOMAIN.DOMAIN HERE
    - N8N_PORT=5678
    - N8N_PROTOCOL=https
    - NODE_ENV=production
    - WEBHOOK_URL=https://I ENTERED MY SUBDOMAIN.DOMAIN HERE/
    - GENERIC_TIMEZONE=America/New_York
    volumes:
    - n8n_data:/home/node/.n8n
    - ./local-files:/files

    cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token I PASTED MY TOKEN HERE
    restart: always
    depends_on:
    - n8n

volumes:
n8n_data:
traefik_data:

I just followed the docker compose setup in n8n’s documentation Here. As you can see I pasted the additions you made. My other guess is that the traefik service is interfering in some way and I wasn’t supposed to have that portion in the yaml file. Any help or suggestions are appreciated, thank you.

1 Like

@SomeDay See here another member made a video about it https://www.youtube.com/watch?v=OPO28M3xOO0

2 Likes

Thank you! I followed that and it is all working perfectly now. I greatly appreciate the work you put into figuring this out for the rest of the community!

1 Like

Do webhooks work? How have you solved that?

@drshajul
Yes they work,

  - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
  - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
  - N8N_PROTOCOL=https

You need to specify your domain name in the .env or above, using cloudflare is very easy method in having a domain name too.

Samuel