If you came here searching for tunnel_secret on the cloudflare_zero_trust_tunnel_cloudflared_token data source, here's the short answer: it isn't there. That data source returns a token, and tunnel_secret is an argument on a different object entirely. The two get conflated constantly because they both sound like "the secret thing that authenticates the tunnel." They're not the same, and the Cloudflare provider v5 schema is explicit about it. Let me lay out both, then show a full example that wires them together correctly.
The two things people mix up
There are two secrets in play when you stand up a cloudflared tunnel with the v5 provider:
tunnel_secretis an input you set on the resourcecloudflare_zero_trust_tunnel_cloudflared. It's the password for a locally-managed tunnel.tokenis an output you read from the data sourcecloudflare_zero_trust_tunnel_cloudflared_token. It's what you hand to thecloudflaredconnector to run the tunnel.
You set one; you read the other. Reaching for tunnel_secret on the token data source is looking for the input on the output.
The data source schema (v5)
Here's the actual schema for cloudflare_zero_trust_tunnel_cloudflared_token in provider v5:
data "cloudflare_zero_trust_tunnel_cloudflared_token" "this" {
account_id = "699d98642c564d2e855e9661899b7252" # required
tunnel_id = "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415" # required
}
- Required:
account_id(String),tunnel_id(String, the tunnel UUID). - Read-Only:
token(String, Sensitive) — the tunnel token used to authenticate the connector.
That's the whole surface. No tunnel_secret attribute, computed or otherwise. Note the v5 argument is tunnel_id; if you're porting from an older config that referenced the tunnel differently, this is one of the rename gotchas in the v5 rewrite.
Where tunnel_secret actually lives
tunnel_secret is an optional, sensitive argument on the resource, and its rules are specific:
tunnel_secret(String, Sensitive) — Sets the password required to run a locally-managed tunnel. Must be at least 32 bytes and encoded as a base64 string.
Two things follow from that:
- It's tied to locally-managed tunnels (
config_src = "local"or"cloudflare"), and you supply it, Cloudflare doesn't hand it back. - It must be ≥ 32 bytes, base64-encoded. Feed it a short or non-base64 string and the apply fails.
Don't type a base64 string by hand. Generate it.
A full working example
This creates the tunnel, generates a valid tunnel_secret, and reads the connector token, all in one config:
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
variable "account_id" {
type = string
}
# 32 random bytes, exposed base64-encoded — exactly what tunnel_secret wants.
resource "random_bytes" "tunnel_secret" {
length = 32
}
resource "cloudflare_zero_trust_tunnel_cloudflared" "this" {
account_id = var.account_id
name = "blog"
config_src = "cloudflare"
tunnel_secret = random_bytes.tunnel_secret.base64_std
}
# The token data source: account_id + the tunnel's UUID (its id).
data "cloudflare_zero_trust_tunnel_cloudflared_token" "this" {
account_id = var.account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.this.id
}
output "tunnel_token" {
value = data.cloudflare_zero_trust_tunnel_cloudflared_token.this.token
sensitive = true
}
Then run the connector with the token:
cloudflared tunnel run --token "$(terraform output -raw tunnel_token)"
A few things worth calling out:
random_bytes.base64_stdgives you a base64 string of exactly the byte length you asked for.length = 32satisfies the "at least 32 bytes" rule with nothing to round.tunnel_id = ...this.id— the resource'sidis the tunnel UUID, so the data source keys off the resource you just made. That dependency also orders the apply correctly.- Both are sensitive. Mark the output
sensitive = trueand keep in mind Terraform state stores both values in plaintext. Treat the state file accordingly (encrypted remote backend, restricted access).
The gotchas, collected
tunnel_secretis not returned by the token data source. It's an input on the resource. If you need the secret elsewhere, referencerandom_bytes.tunnel_secret.base64_std, not the data source.tunnel_secretmust be ≥ 32 bytes, base64. Generate it; don't hand-write it.- In v5 the data source argument is
tunnel_id, and the value you pass is the resource'sid. - The
tokenyou actually runcloudflaredwith comes from the data source, and it's what most tutorials mean by "the tunnel token." - State holds both secrets in the clear. A remote backend with encryption isn't optional here.
The takeaway
The confusion is entirely a naming collision: tunnel_secret (the password you set on the resource) and token (the connector credential you read from the data source) are different objects doing different jobs. Once you see that the data source only ever gives you token, the v5 config falls out cleanly: generate the secret with random_bytes, set it on the resource, read the token from the data source, run the connector. Everything above applies to the cloudflare/cloudflare v5 provider.