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

  1. Remove one of the two fields: keep certFile if the cert is on disk, or certData (base64 of the PEM) if inline.
  2. Inspect how config is merged/templated to find where the second value is injected, and unset it.
  3. 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

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


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/b5987be7dc9b3f78. Report an issue: GitHub.