grafana/k6 · error
failed to decode PEM key
Error message
failed to decode PEM key
What it means
When client.connect() is given TLS certificates that include a password, k6 decrypts the private key via decryptPrivateKey (lifted from lib/options.go). pem.Decode must find a PEM block; a key that is not PEM-encoded (binary DER, wrong file, truncated copy-paste, empty content) yields a nil block and this error.
Source
Thrown at internal/js/modules/k6/grpc/client.go:128
defer func() { _ = fdsetFile.Close() }()
fdsetBytes, err := io.ReadAll(fdsetFile)
if err != nil {
return nil, fmt.Errorf("couldn't read protoset: %w", err)
}
fdset := &descriptorpb.FileDescriptorSet{}
if err = proto.Unmarshal(fdsetBytes, fdset); err != nil {
return nil, fmt.Errorf("couldn't unmarshal protoset file %s: %w", protosetPath, err)
}
return c.convertToMethodInfo(fdset)
}
// Note: this function was lifted from `lib/options.go`
func decryptPrivateKey(key, password []byte) ([]byte, error) {
block, _ := pem.Decode(key)
if block == nil {
return nil, errors.New("failed to decode PEM key")
}
blockType := block.Type
if blockType == "ENCRYPTED PRIVATE KEY" {
return nil, errors.New("encrypted pkcs8 formatted key is not supported")
}
/*
Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still
being used here because it is deprecated due to it not supporting *good* cryptography
ultimately though we want to support something so we will be using it for now.
*/
decryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck
if err != nil {
return nil, err
}
key = pem.EncodeToMemory(&pem.Block{
Type: blockType,
Bytes: decryptedKey,View on GitHub (pinned to 93accf6570)
Solutions
- Verify the key content starts with -----BEGIN RSA PRIVATE KEY----- (or EC/PRIVATE KEY) and ends with -----END ...-----
- Convert DER to PEM: openssl rsa -inform DER -in key.der -out key.pem
- Check the argument order in the certs triple [cert, key, password] and that the key file was read completely
Example fix
// before: keyFile is DER/binary
client.connect(addr, { tls: { certs: [[certPem, keyDer, 'pass']] } });
// after: convert once, then
// openssl rsa -inform DER -in key.der -out key.pem
client.connect(addr, { tls: { certs: [[certPem, keyPem, 'pass']] } }); Defensive patterns
Strategy: validation
Validate before calling
function isPemKey(s) {
return typeof s === 'string' && /^-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(s.trim());
}
if (!isPemKey(keyPem)) throw new Error('tls key is not PEM-encoded (missing BEGIN PRIVATE KEY fence)');
client.connect(addr, { tls: { certs: [[certPem, keyPem, password]] } }); Prevention
- Check for the BEGIN/END PRIVATE KEY fences before connecting
- Never point the key slot at DER files -- convert with openssl first
- Read cert material from dedicated secret files, not templated strings, to avoid fence corruption
When it happens
Trigger: client.connect(addr, { tls: { certs: [[certPem, keyPem, password]] } }) where keyPem is DER/binary, is actually the certificate or CA file, or has lost its '-----BEGIN ... PRIVATE KEY-----' header/footer.
Common situations: Pointing the key slot at a .der or .crt file; PEM fences broken by templating or YAML/JSON escaping; key variable left empty so another value is passed by mistake.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to append ca certificate [%d] from PEM
- encrypted pkcs8 formatted key is not supported
- failed to append root certificate to the pool
- failed to append certificate from PEM: %w
- invalid plaintext value: '%#v', it needs to be boolean
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/12d36c1ecc18343f.
Report an issue: GitHub.