temporalio/temporal · error · ErrTLSConfig
only one of caData or caFile properties should be specified
Error message
only one of caData or caFile properties should be specified
What it means
This error is returned by validateTemporalTls when the CA certificate is supplied both inline (CaData) and via a file path (CaFile). Like the cert/key checks, the library requires a single CA source so it can deterministically build the x509.CertPool, wrapping the failure with ErrTLSConfig.
Source
Thrown at common/auth/tls_config_helper.go:135
}
func validateTemporalTls(temporalTls *TLS) error {
if temporalTls.CertData != "" && temporalTls.CertFile != "" {
return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of certData or certFile properties should be specified")
}
if temporalTls.KeyData != "" && temporalTls.KeyFile != "" {
return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of keyData or keyFile properties should be specified")
}
certProvided := temporalTls.CertData != "" || temporalTls.CertFile != ""
keyProvided := temporalTls.KeyData != "" || temporalTls.KeyFile != ""
if certProvided != keyProvided {
return fmt.Errorf("%w: %s", ErrTLSConfig, "cert or key is missing")
}
if temporalTls.CaData != "" && temporalTls.CaFile != "" {
return fmt.Errorf("%w: %s", ErrTLSConfig, "only one of caData or caFile properties should be specified")
}
return nil
}
func parseCAs(temporalTls *TLS) (*x509.CertPool, error) {
var caBytes []byte
var err error
if temporalTls.CaFile != "" {
caBytes, err = os.ReadFile(temporalTls.CaFile)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to read client ca file", err)
}
} else if temporalTls.CaData != "" {
caBytes, err = base64.StdEncoding.DecodeString(temporalTls.CaData)
if err != nil {
return nil, fmt.Errorf("%w: %s (%w)", ErrTLSConfig, "unable to decode client ca data", err)
}
}View on GitHub (pinned to bde624efd1)
Solutions
- Remove one field: keep caFile for on-disk PEM, or caData with base64-encoded PEM.
- Trace the config merge to find where the duplicate CA source is added and unset it.
- Fail fast in tests by asserting only one CA source is set per TLS block.
Example fix
// before tls: caData: "LS0tLS1CRUdJTi..." caFile: "/etc/temporal/certs/ca.pem" // after tls: caFile: "/etc/temporal/certs/ca.pem"
Defensive patterns
Strategy: validation
Validate before calling
func validateTLSOneCaSource(t *auth.TLS) error {
if t.CaData != "" && t.CaFile != "" {
return fmt.Errorf("set only one of caData or caFile")
}
return nil
} Type guard
func hasExactlyOne(a, b string) bool { return (a != "") != (b != "") } Prevention
- Pick a single CA delivery mechanism per environment and enforce it in config templates
- Null out the old CA field in overrides when adding a new one
- Include a config lint step in CI that flags both *Data and *File set simultaneously
When it happens
Trigger: Calling NewTLSConfig with a *TLS struct where both CaData != "" and CaFile != "" — typically from merged config layers or a templating error.
Common situations: Adding a custom CA via file mount while an inline CA remains from a base config; Helm/environment overlay injecting caData on top of caFile; example-config copy-paste filling both fields.
Related errors
- only one of certData or certFile properties should be specif
- only one of keyData or keyFile properties should be specifie
- cert or key is missing
- invalid value for publicClient.forceTLSConfig: %q
- host name cannot be empty string
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/2629874feeaf8e8c.
Report an issue: GitHub.