temporalio/temporal · error · ErrTLSConfig
only one of certData or certFile properties should be specif
Error message
only one of certData or certFile properties should be specified
What it means
This error is returned by validateTemporalTls in the Temporal TLS config helper when a TLS block specifies the certificate both inline (CertData) and via a file path (CertFile). The library requires exactly one source for the certificate so it can unambiguously load it, and it wraps the failure with ErrTLSConfig so callers can detect TLS misconfiguration.
Source
Thrown at common/auth/tls_config_helper.go:121
if caCertPool != nil {
tlsConfig.RootCAs = caCertPool
}
// Load client cert
clientCert, err := parseClientCert(temporalTls)
if err != nil {
return nil, err
}
if clientCert != nil {
tlsConfig.Certificates = []tls.Certificate{*clientCert}
}
return tlsConfig, nil
}
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
}
View on GitHub (pinned to bde624efd1)
Solutions
- Remove one of the two fields: keep certFile if the cert is on disk, or certData (base64 of the PEM) if inline.
- Inspect how config is merged/templated to find where the second value is injected, and unset it.
- Validate your TLS config before startup (e.g. call validateTemporalTls or replicate its checks in config tests).
Example fix
// before tls: certData: "LS0tLS1CRUdJTi..." certFile: "/etc/temporal/certs/client.pem" // after tls: certFile: "/etc/temporal/certs/client.pem"
Defensive patterns
Strategy: validation
Validate before calling
func validateTLSOneCertSource(t *auth.TLS) error {
if t.CertData != "" && t.CertFile != "" {
return fmt.Errorf("set only one of certData or certFile")
}
return nil
} Type guard
func hasExactlyOne(a, b string) bool { return (a != "") != (b != "") } Prevention
- Keep each cert in exactly one place: file on disk OR inline base64, per environment
- Add a unit test that unmarshals your config and asserts no TLS field sets both *Data and *File
- When merging config layers, explicitly null out the superseded field in overrides
When it happens
Trigger: Calling NewTLSConfig with a *TLS struct where both CertData != "" and CertFile != "". This happens with hand-written YAML/JSON config where a cert was set inline and later a file path was added, or when config layers (defaults + overrides + env) merge both fields.
Common situations: Merging base config files with per-environment overrides so both certData and certFile end up populated; templating mistakes that render both fields; copying an example config and filling in both fields; Kubernetes secrets mounted as files while an older inline value remains in a ConfigMap.
Related errors
- only one of keyData or keyFile properties should be specifie
- cert or key is missing
- only one of caData or caFile properties should be specified
- 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/b5987be7dc9b3f78.
Report an issue: GitHub.