hashicorp/terraform · error
client_private_key_pem is set but client_certificate_pem is
Error message
client_private_key_pem is set but client_certificate_pem is not
What it means
Inverse of 245: client_private_key_pem (or TF_HTTP_CLIENT_PRIVATE_KEY_PEM) is set but client_certificate_pem is empty. tls.X509KeyPair needs both, so configureTLS rejects the half-configured pair at Configure time rather than failing later in the handshake.
Source
Thrown at internal/backend/remote-state/http/backend.go:279
configVal, "client_ca_certificate_pem",
"TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", cty.StringVal(""),
).AsString()
clientCertificatePem := backendbase.GetAttrEnvDefaultFallback(
configVal, "client_certificate_pem",
"TF_HTTP_CLIENT_CERTIFICATE_PEM", cty.StringVal(""),
).AsString()
clientPrivateKeyPem := backendbase.GetAttrEnvDefaultFallback(
configVal, "client_private_key_pem",
"TF_HTTP_CLIENT_PRIVATE_KEY_PEM", cty.StringVal(""),
).AsString()
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")
}
}
if clientCertificatePem != "" && clientPrivateKeyPem != "" {View on GitHub (pinned to c9def3e214)
Solutions
- Add the matching client_certificate_pem (PEM).
- Remove client_private_key_pem if mTLS is not intended.
- Synchronize both env vars so they are always provisioned as a pair.
Example fix
// before
backend "http" {
address = "https://state.corp"
client_private_key_pem = file("client.key")
}
// after
backend "http" {
address = "https://state.corp"
client_certificate_pem = file("client.crt")
client_private_key_pem = file("client.key")
} Defensive patterns
Strategy: validation
Validate before calling
func validateMTLSPair(cert, key string) error {
if key != "" && cert == "" {
return fmt.Errorf("client_private_key_pem set without client_certificate_pem")
}
return nil
} Prevention
- Treat cert+key as inseparable in secret templating.
- Fail fast in config rendering if only one is present.
- Pair the env vars in deployment manifests.
When it happens
Trigger: Setting client_private_key_pem without client_certificate_pem, or exporting only TF_HTTP_CLIENT_PRIVATE_KEY_PEM. Fires during `terraform init`.
Common situations: Cert injected by a different secret path that was skipped; key rotated but new cert not yet provisioned; leftover env var from a previous mTLS setup.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client_certificate_pem is set but client_private_key_pem is
- cannot load client certificate: %w
- failed to parse unlock_address URL: %s
- unlock_address must be HTTP or HTTPS
- invalid retry_max: %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/10d1f35e1d640069.
Report an issue: GitHub.