cloudflare/cloudflared · error
The tunnel credentials file should be .json but you gave a .
Error message
The tunnel credentials file should be .json but you gave a .pem. The tunnel credentials file was originally created by `cloudflared tunnel create`. You may have accidentally used the filepath to cert.pem, which is generated by `cloudflared tunnel login`.
What it means
readTunnelCredentials parses the tunnel credentials file as JSON. If json.Unmarshal fails and the file has a .pem extension, cloudflared raises this dedicated error to tell you the wrong file was passed: credentials files (created by `cloudflared tunnel create`) are JSON, whereas cert.pem (created by `cloudflared tunnel login`) is a PEM certificate. The .pem check exists purely to produce this actionable hint instead of a generic invalid-JSON error.
Source
Thrown at cmd/cloudflared/tunnel/subcommand_context.go:116
sc.userCredential = uc
}
return sc.userCredential, nil
}
func (sc *subcommandContext) readTunnelCredentials(credFinder CredFinder) (connection.Credentials, error) {
filePath, err := credFinder.Path()
if err != nil {
return connection.Credentials{}, err
}
body, err := sc.fs.readFile(filePath)
if err != nil {
return connection.Credentials{}, errors.Wrapf(err, "couldn't read tunnel credentials from %v", filePath)
}
var credentials connection.Credentials
if err = json.Unmarshal(body, &credentials); err != nil {
if filepath.Ext(filePath) == ".pem" {
return connection.Credentials{}, fmt.Errorf("The tunnel credentials file should be .json but you gave a .pem. " +
"The tunnel credentials file was originally created by `cloudflared tunnel create`. " +
"You may have accidentally used the filepath to cert.pem, which is generated by `cloudflared tunnel " +
"login`.")
}
return connection.Credentials{}, invalidJSONCredentialError{path: filePath, err: err}
}
return credentials, nil
}
func (sc *subcommandContext) create(name string, credentialsFilePath string, secret string) (*cfapi.Tunnel, error) {
client, err := sc.client()
if err != nil {
return nil, errors.Wrap(err, "couldn't create client to talk to Cloudflare Tunnel backend")
}
var tunnelSecret []byte
if secret == "" {
tunnelSecret, err = generateTunnelSecret()View on GitHub (pinned to 2253eeeb25)
Solutions
- Run `cloudflared tunnel list` to find your tunnel, then point --credentials-file at <tunnel-id>.json in ~/.cloudflared (or ~/.cloudflared/config.json's credentials-file key).
- If no credentials JSON exists (tunnel created on another machine), recreate the tunnel with `cloudflared tunnel create <name>` to generate one.
- If you actually need to authenticate cloudflared itself, run `cloudflared tunnel login` to produce cert.pem — but never pass cert.pem as credentials-file.
Example fix
// before cloudflared tunnel run --credentials-file ~/.cloudflared/cert.pem my-tunnel // after cloudflared tunnel run --credentials-file ~/.cloudflared/<tunnel-uuid>.json my-tunnel
Defensive patterns
Strategy: validation
Validate before calling
path := "~/.cloudflared/cert.pem" // whatever you'd pass
if filepath.Ext(path) == ".pem" {
return fmt.Errorf("refusing to pass %s as credentials-file; expected a JSON file like <tunnel-id>.json", path)
}
if _, err := os.Stat(path); err != nil {
return err
} Prevention
- Always pass <tunnel-id>.json (from `cloudflared tunnel create`) as --credentials-file, never cert.pem.
- Remember cert.pem = login credentials; <id>.json = tunnel credentials — keep them in separate variables in scripts.
- Validate the file extension and that it starts with '{' before invoking cloudflared.
When it happens
Trigger: Calling a tunnel command (e.g. `cloudflared tunnel run --credentials-file <path>`, or findCredentials/findID resolving credentials) with the path to cert.pem instead of <tunnel-id>.json.
Common situations: Developers pass ~/.cloudflared/cert.pem because it is the only file in ~/.cloudflared after running `cloudflared tunnel login`; shell autocompletion picks cert.pem; a script variables the wrong path in TUNNEL_CREDENTIALS.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- --unix-socket must be used exclusively.
- Specified origin urls using both --url and argument. Decide
- ErrURLIncompatibleWithIngress
- hostname and url shouldn't match. See --help for more inform
- Cannot parse tag value %s
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/dc92577975b899df.
Report an issue: GitHub.