External Access - Cloudflare Tunnel¶
What you'll end up with¶
Selected services reachable from the public internet via a Cloudflare Zero Trust tunnel. No inbound ports on your router, works behind CGNAT. The tunnel runs as a Docker container in CT 100 (cloudflared stack), connecting outbound to Cloudflare.
Prerequisites¶
- Komodo running (CT 100, Traefik deployed)
- A Cloudflare account with your domain added
- Cloudflare Zero Trust enabled (free tier is sufficient)
1. Create a tunnel¶
In Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel:
- Choose Cloudflared as the connector type
- Name it (e.g.
homelab) - Copy the tunnel token shown - you'll add it to the
cloudflaredstack.env
2. Configure the stack .env¶
The CT script placed a .env at /data/config/management/komodo/periphery/stacks/cloudflared/.env. Fill in the token:
3. Deploy via Komodo¶
In Komodo UI: Stacks → Add Stack → cloudflared, server management, files on host, deploy.
After a few seconds the tunnel should appear as Healthy in Cloudflare Zero Trust → Tunnels.
4. Add a public hostname¶
In Cloudflare Zero Trust → Tunnels → your tunnel → Public Hostname → Add:
| Field | Value |
|---|---|
| Subdomain | service (e.g. panel) |
| Domain | example.com |
| Type | HTTP |
| URL | 192.168.50.100:80 (Traefik, plain HTTP internally) |
This creates a public DNS record and routes https://service.example.com → Cloudflare → tunnel → Traefik → service.
Scope public routes carefully
Only expose the minimum. Each public hostname should route to one specific service via Traefik. Don't create a wildcard or route to the Traefik dashboard.
5. Add the Traefik route¶
For each publicly exposed service, add a Traefik route file on CT 100. The public hostname hits Traefik on port 80 (plain HTTP from the tunnel - TLS is terminated at Cloudflare). Route it to the service backend:
http:
routers:
mypanel-public:
rule: "Host(`panel.example.com`)"
entryPoints:
- web
service: mypanel
services:
mypanel:
loadBalancer:
servers:
- url: "http://192.168.50.40:80"
Place this file in /data/config/management/traefik/config/ on CT 100. Traefik hot-reloads it.
Don't skip access controls on public routes
Internal routes are safe behind Pi-hole + Tailscale. Public routes are reachable by anyone. Use Cloudflare Access policies (Zero Trust → Access → Applications) to gate anything sensitive, or ensure the service itself requires authentication.
Stopping the tunnel¶
If you want to stop public access without losing the config:
- Stop the stack in Komodo (or
docker stop cloudflaredin CT 100) - Disable the Traefik route - rename the route file to add
.disabled(don't just stop the container; the route file stays active in Traefik and any device with the rightHostheader can still reach it)
pct exec 100 -- mv /data/config/management/traefik/config/cloudflared-public.yml \
/data/config/management/traefik/config/cloudflared-public.yml.disabled
To reactivate: rename the file back and start the stack in Komodo.
Day-2 operations¶
Updating cloudflared¶
cloudflared is a Docker container managed by Komodo. In Komodo UI: Stacks → cloudflared → Deploy. Pulls the latest image and restarts.
Cloudflare's connector auto-updates itself by default, but a manual deploy will also pick up any image changes.
Limitations¶
- Cloudflare Tunnel only supports HTTP/HTTPS, not raw TCP - you can't tunnel game server ports (UDP) or SFTP through it
- Latency is higher than Tailscale due to Cloudflare's routing; not suitable for latency-sensitive services
- For Minecraft: use Tailscale sharing instead (see Tailscale guide)
Troubleshooting¶
Tunnel shows as unhealthy in Cloudflare
- Check the container is running:
pct exec 100 -- docker ps | grep cloudflared - Check logs:
pct exec 100 -- docker logs cloudflared --tail 50 - Confirm the token in
.envmatches what Cloudflare shows for the tunnel
Public hostname returning 502
Traefik is receiving the request but can't reach the backend. Check:
- The route file URL is correct and the service is running
- The route is using entryPoints: [web] (port 80), not websecure - the tunnel sends plain HTTP to Traefik