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 error 248: client_private_key_pem (or TF_HTTP_CLIENT_PRIVATE_KEY_PEM) is non-empty while client_certificate_pem (or TF_HTTP_CLIENT_CERTIFICATE_PEM) is empty. A private key alone cannot complete a TLS client-auth handshake; the certificate is required.

Solutions

  1. Set client_certificate_pem to the PEM certificate matching the private key.
  2. If mTLS is not required, remove client_private_key_pem entirely.
  3. Ensure TF_HTTP_CLIENT_CERTIFICATE_PEM and TF_HTTP_CLIENT_PRIVATE_KEY_PEM are both exported.

Example fix

// before
client_private_key_pem = file("client.key")
// client_certificate_pem missing
// after
client_certificate_pem = file("client.crt")
client_private_key_pem = file("client.key")
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight: key and cert must both be set (or both unset)
cert="${TF_HTTP_CLIENT_CERTIFICATE_PEM:-}"
key="${TF_HTTP_CLIENT_PRIVATE_KEY_PEM:-}"
if [ -n "$key" ] && [ -z "$cert" ]; then
  echo "ERROR: client_private_key_pem set but client_certificate_pem is empty"; exit 1
fi

Prevention

When it happens

Trigger: Private key supplied but the matching certificate was omitted.

Common situations: Certificate stored separately and not referenced; cert line dropped during templating; only the key was migrated to a new config.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/10d1f35e1d640069. Report an issue: GitHub.

Appendix: 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 d32a084675)