temporalio/temporal · error · ErrTLSConfig
cert or key is missing
Error message
cert or key is missing
What it means
This error is returned by validateTemporalTls when a certificate is provided without a matching private key, or vice versa: certProvided != keyProvided. A usable mTLS identity requires both, so the library rejects the partial config and wraps it with ErrTLSConfig.
Source
Thrown at common/auth/tls_config_helper.go:131
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
}
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)View on GitHub (pinned to bde624efd1)
Solutions
- Add the missing counterpart: set both cert (CertData or CertFile) and key (KeyData or KeyFile).
- If mTLS identity is not needed, remove the cert/key fields entirely rather than leaving only one.
- Verify secret/file mounts so both halves of the pair are delivered to the process.
Example fix
// before tls: certFile: "/etc/temporal/certs/client.pem" // after tls: certFile: "/etc/temporal/certs/client.pem" keyFile: "/etc/temporal/certs/client.key"
Defensive patterns
Strategy: validation
Validate before calling
func validateTLSPair(t *auth.TLS) error {
cert := t.CertData != "" || t.CertFile != ""
key := t.KeyData != "" || t.KeyFile != ""
if cert != key {
return fmt.Errorf("TLS cert and key must both be provided together")
}
return nil
} Type guard
func hasCompleteIdentity(t *auth.TLS) bool {
cert := t.CertData != "" || t.CertFile != ""
key := t.KeyData != "" || t.KeyFile != ""
return cert == key
} Prevention
- Always store cert+key as a pair in the same secret/mount and template them together
- During rotation, update both halves in one operation
- Add a startup assertion that cert and key presence match before calling NewTLSConfig
When it happens
Trigger: Calling NewTLSConfig where one side is set — e.g. CertFile set but neither KeyData nor KeyFile, or KeyData set with no CertData/CertFile.
Common situations: Config templating where the key is injected separately from the cert (and one injection fails); someone commenting out the key field; rotating certs and replacing only one half of the pair; mounting only the cert secret in Kubernetes.
Related errors
- only one of certData or certFile properties should be specif
- only one of keyData or keyFile properties should be specifie
- 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/97b725b80b8c492d.
Report an issue: GitHub.