hashicorp/terraform · error

failed to create cert signer %q: %s

Error message

failed to create cert signer %q: %s

What it means

Raised in signCertWithPrivateKey when ssh.NewCertSigner fails. Both the private key and certificate parsed successfully and a signer was created, but the certificate could not be bound to the signer. The most common cause is a key/cert type mismatch: the certificate was issued for a different public key than the private key supplied.

Source

Thrown at internal/communicator/ssh/provisioner.go:415

func signCertWithPrivateKey(pk string, certificate string) (ssh.AuthMethod, error) {
	rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
	if err != nil {
		return nil, fmt.Errorf("failed to parse private key %q: %s", pk, err)
	}

	pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certificate))
	if err != nil {
		return nil, fmt.Errorf("failed to parse certificate %q: %s", certificate, err)
	}

	usigner, err := ssh.NewSignerFromKey(rawPk)
	if err != nil {
		return nil, fmt.Errorf("failed to create signer from raw private key %q: %s", rawPk, err)
	}

	ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)
	if err != nil {
		return nil, fmt.Errorf("failed to create cert signer %q: %s", usigner, err)
	}

	return ssh.PublicKeys(ucertSigner), nil
}

func readPrivateKey(pk string) (ssh.AuthMethod, error) {
	// We parse the private key on our own first so that we can
	// show a nicer error if the private key has a password.
	block, _ := pem.Decode([]byte(pk))
	if block == nil {
		return nil, errors.New("Failed to read ssh private key: no key found")
	}
	if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
		return nil, errors.New(
			"Failed to read ssh private key: password protected keys are\n" +
				"not supported. Please decrypt the key prior to use.")
	}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Ensure the private_key and certificate come from the same key pair (the cert's public key must match the private key's public key).
  2. Regenerate both the key pair and certificate together with ssh-keygen -s ca_key -I identity id_ed25519.pub.
  3. Verify the match: compare ssh-keygen -y -f private_key with the cert's embedded key via ssh-keygen -L -f cert.
  4. Check for stale certificate files from a previous key rotation.

Example fix

// before
connection {
  private_key = file("~/.ssh/id_rsa")      # RSA key
  certificate = file("~/.ssh/ed25519-cert.pub") # cert for a DIFFERENT key
}

// after
connection {
  private_key = file("~/.ssh/id_ed25519")
  certificate = file("~/.ssh/id_ed25519-cert.pub")
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate that the private key and certificate belong to the same key pair
func validateKeyCertPair(pk, cert string) error {
    rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
    if err != nil {
        return fmt.Errorf("invalid private key: %w", err)
    }
    signer, err := ssh.NewSignerFromKey(rawPk)
    if err != nil {
        return fmt.Errorf("cannot create signer: %w", err)
    }
    pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(cert))
    if err != nil {
        return fmt.Errorf("invalid certificate: %w", err)
    }
    sshCert, ok := pcert.(*ssh.Certificate)
    if !ok {
        return errors.New("not a certificate")
    }
    if !bytes.Equal(signer.PublicKey().Marshal(), sshCert.Key.Marshal()) {
        return errors.New("private key and certificate public keys do not match")
    }
    return nil
}

Prevention

When it happens

Trigger: ssh.NewCertSigner validates that the certificate's public key matches the signer's public key. If the private_key and certificate belong to different key pairs, or the certificate type is incompatible with the signer type, this fails.

Common situations: The private_key and certificate were generated from different key pairs (e.g. cert for id_rsa but private_key set to id_ed25519), the certificate was issued for a different key after regeneration, or a stale certificate is paired with a new key.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/00e6842f5de6e4d7. Report an issue: GitHub.