cloudflared connects outbound from the cluster to Cloudflare’s edge. Public hostnames in the Zero Trust dashboard route to in-cluster Services over HTTP or HTTPS. Nothing needs to listen on the WAN.
Manifests live in the public homelabs repo under cloudflare/. Which apps get a hostname, which origin URL they use, and whether Cloudflare Access sits in front of them belong on those apps’ pages. This page only covers the connector.
Create the tunnel in Zero Trust#
- Open Cloudflare Zero Trust → Networks → Tunnels.
- Create a tunnel → choose Cloudflared.
- Name the tunnel.
- On the Install connector step, copy the tunnel token, which you need for the Kubernetes Secret below.
- Finish the wizard; add Public Hostnames later (or now) for each service you want to expose.
Keep the token out of Git. Store it in a local secret.yaml (gitignored) or in your secret manager and inject at deploy time.
Deploy cloudflared in Kubernetes#
From a checkout of homelabs (or the equivalent path in your bootstrap flow):
kubectl apply -f cloudflare/namespace.yaml
cp cloudflare/secret.yaml.template cloudflare/secret.yaml
# Edit cloudflare/secret.yaml — set tunnel-token to the value from Zero Trust
kubectl apply -f cloudflare/secret.yaml
kubectl apply -f cloudflare/deployment.yamlThe Deployment runs a single cloudflared replica in namespace cloudflare-tunnel. It reads TUNNEL_TOKEN from Secret cloudflare-tunnel-token and executes tunnel run.
Confirm the connector registered:
kubectl -n cloudflare-tunnel get pods
kubectl -n cloudflare-tunnel logs -f deployment/cloudflaredHealthy logs show the connector registered with Cloudflare and tunnels ready.
Add a public hostname#
For each service, add a Public Hostname on the same tunnel in Zero Trust (Networks → Tunnels → your tunnel → Public Hostname).
| Field | Typical value |
|---|---|
| Subdomain | The public name you want (app, …) |
| Domain | Your public domain |
| Type | HTTP for plain in-cluster HTTP; HTTPS when the Service speaks TLS |
| URL | In-cluster DNS name and port (http://<service>.<namespace>.svc.cluster.local:<port>) |
If the Service uses a self-signed certificate, enable No TLS Verify. Leave Cloudflare Access off when the hostname must stay reachable for OIDC discovery, JWKS, or an identity-provider callback. Put that decision on the app’s own page rather than here.
Changes in the dashboard take effect without restarting cloudflared; the connector picks up route updates from Cloudflare.
Security model#
cloudflared initiates connections to Cloudflare; nothing listens on the WAN. The tunnel token grants access only to that tunnel’s configuration; rotate it in Zero Trust if it leaks. Some apps use the tunnel for browser login (OIDC). The Kubernetes API server stays on a private path (VPN/LAN). See Dex OIDC.
Troubleshooting#
| Symptom | Check |
|---|---|
Pod CrashLoopBackOff | Invalid or expired tunnel token in cloudflare-tunnel-token |
| Hostname 502 / error | Public Hostname origin URL wrong, or target Service/pod not running |
| Connector not in dashboard | Pod logs; cluster egress to Cloudflare; token matches the tunnel |
kubectl -n cloudflare-tunnel describe pod -l app=cloudflared
kubectl -n cloudflare-tunnel logs deployment/cloudflared --tail=100