opentofu/opentofu · error
client_certificate_pem is set but client_private_key_pem is
Error message
client_certificate_pem is set but client_private_key_pem is not
What it means
configureTLS requires the mTLS pair to be complete: if client_certificate_pem is non-empty while client_private_key_pem is empty, backend configuration aborts with this error before any TLS settings are applied. The two attributes are only meaningful together because tls.X509KeyPair consumes both.
Source
Thrown at internal/backend/remote-state/http/backend.go:176
type Backend struct {
*schema.Backend
encryption encryption.StateEncryption
client *httpClient
}
// configureTLS configures TLS when needed; if there are no conditions requiring TLS, no change is made.
func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.ResourceData) error {
// If there are no conditions needing to configure TLS, leave the client untouched
skipCertVerification := data.Get("skip_cert_verification").(bool)
clientCACertificatePem := data.Get("client_ca_certificate_pem").(string)
clientCertificatePem := data.Get("client_certificate_pem").(string)
clientPrivateKeyPem := data.Get("client_private_key_pem").(string)
if !skipCertVerification && clientCACertificatePem == "" && clientCertificatePem == "" && clientPrivateKeyPem == "" {
return nil
}
if clientCertificatePem != "" && clientPrivateKeyPem == "" {
return fmt.Errorf("client_certificate_pem is set but client_private_key_pem is not")
}
if clientPrivateKeyPem != "" && clientCertificatePem == "" {
return fmt.Errorf("client_private_key_pem is set but client_certificate_pem is not")
}
// TLS configuration is needed; create an object and configure it
var tlsConfig tls.Config
client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = &tlsConfig
if skipCertVerification {
// ignores TLS verification
tlsConfig.InsecureSkipVerify = true
}
if clientCACertificatePem != "" {
// trust servers based on a CA
tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(clientCACertificatePem)) {
return errors.New("failed to append certs")View on GitHub (pinned to 3561785c48)
Solutions
- Add the matching client_private_key_pem attribute to the backend block
- If you only meant to trust a custom CA, remove client_certificate_pem and keep client_ca_certificate_pem
- Check attribute spelling when passing via -backend-config so the value does not silently read as empty
Example fix
# before
backend "http" {
address = "https://state.example.com"
client_certificate_pem = file("client.crt")
}
# after
backend "http" {
address = "https://state.example.com"
client_certificate_pem = file("client.crt")
client_private_key_pem = file("client.key")
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight the mTLS pair before running tofu init
cert := os.Getenv("TF_CLIENT_CERT")
key := os.Getenv("TF_CLIENT_KEY")
if cert != "" && key == "" {
log.Fatal("client_certificate_pem is set but client_private_key_pem is missing")
} Prevention
- Treat client_certificate_pem and client_private_key_pem as one unit in templates and reviews
- Add a CI lint that fails when exactly one of the two attributes appears
- Watch for _pem suffix typos when passing via -backend-config
When it happens
Trigger: backend "http" config with client_certificate_pem set but client_private_key_pem omitted; passing the pair via repeated -backend-config flags where the key flag is missing or its attribute name is misspelled (e.g. client_private_key without the _pem suffix).
Common situations: Splitting cert and key into separate files/variables and forgetting the second -backend-config flag; merging config snippets where one side of the pair is dropped; renaming attributes during a migration to the _pem names.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client_private_key_pem is set but client_certificate_pem is
- cannot load client certificate: %w
- failed to append certs
- failed to parse address URL: %w
- failed to parse lockAddress URL: %w
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/c801f1bc20fb92e2.
Report an issue: GitHub.