Cloudflared: Secure Tunnels for Kubernetes

Introduction
Cloudflared is the magic wand for exposing our local services to the world securely. It creates a private tunnel from our Kubernetes cluster directly to the Cloudflare network.
We use it to expose our Kubernetes services to the internet without opening any public ingress ports. It essentially treats your cluster as if it were part of a private network, with Cloudflare acting as the gateway. This "zero trust" approach significantly reduces our attack surface.
How it works
Instead of incoming traffic hitting our public IP, cloudflared initiates an outbound connection to Cloudflare. Users access our services via Cloudflare's edge, which validates the request and sends it through the established tunnel to our pod.
graph LR
User[User] -->|HTTPS| CF[Cloudflare Edge]
subgraph Kubernetes Cluster
Cloudflared[cloudflared Pod]
Service[Internal Service]
end
CF <-->|Encrypted Tunnel| Cloudflared
Cloudflared -->|HTTP| Service Prerequisites
Before installing the chart, we need to create a tunnel in the Cloudflare Zero Trust Dashboard or via the CLI. Since the community helm chart is using "Local Management" (managing config via code), we'll use the CLI.
Following the official documentation, we'll install cloudflared locally to authenticate, generate the tunnel and name it:
Now, we login and create a new tunnel named my-k8s-tunnel:
You should see output confirming the tunnel creation with its ID:
You'll also have locally 2 files:
We'll use this files to configure the chart later.
Configuration
We need to provide the chart with our credentials and define the ingress rules. We'll check the contents of our local credentials files and pass them as secrets (base64 encoded) or values.
Here is a robust values-overrides.yaml configuration. Note that we are configuring it for high availability.
Now, let's deploy it:
Tip
A good practice is not to expose directly your pods but the Ingress Gateway directly (Nginx, Envoy, Traefik, etc.). So then you manage your own Ingress/Httproutes and Cloudflare will only be a reverse proxy.
graph LR
CF[Cloudflare] <-->|Tunnel| Cloudflared[Cloudflared Pod]
Cloudflared -->|Traffic| Gateway[Gateway API or Ingress Gateway]
Gateway -->|Routing| Service[App Service] DNS Configuration
The final step is to tell Cloudflare to route traffic for our domains through this specific tunnel.
We need to create CNAME records pointing to our tunnel's unique address: <Tunnel-UUID>.cfargotunnel.com.
| Type | Name | Content | Proxied |
|---|---|---|---|
CNAME | *.mycompany.com | 00000000-0000-0000-0000-000000000000.cfargotunnel.com | Yes |
CNAME | myservice.mycompany.com | 00000000-0000-0000-0000-000000000000.cfargotunnel.com | Yes |
Bonus: Integration with ExternalDNS and Gateway API
If we are using the Gateway API and external-dns, we can automate the DNS record creation.
Add the tunnel address as a target annotation on your Gateway:
You enter here the target of your tunnel. Then declare an httproute to create the CNAME record:
The combination of the Gateway and the HTTPRoute will create the CNAME record for you.
Best Practices
- High Availability: Use
replica.count: 2(or more) andreplica.allNodes: false(Deployment) ortrue(DaemonSet) to ensure the tunnel persists if a node fails. - Protocol: Setting
protocol: autoallows Cloudflared to negotiate the best transport (QUIC if possible). - Catch-all Rule: Always include
- service: http_status:404at the end of your ingress list.
Resources
- https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/deployment-guides/kubernetes/
- https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/routing-to-tunnel/
- https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/local-management/create-local-tunnel/
- https://community-charts.github.io/docs/charts/cloudflared/usage