grafana/k6 · error
failed to append certificate from PEM: %w
Error message
failed to append certificate from PEM: %w
What it means
grpc.connect() with mutual TLS calls tls.X509KeyPair(tls.cert, tls.key); any failure to build the key pair - invalid PEM in either argument, a certificate that does not match the key, or an unsupported key encoding - is wrapped as 'failed to append certificate from PEM' (internal/js/modules/k6/grpc/client.go:176). Note the exact tls map keys: cert, key, password, cacerts (validated earlier in parseConnectTLSParam).
Source
Thrown at internal/js/modules/k6/grpc/client.go:176
}
}
}
// Ignoring 'TLS MinVersion is too low' because this tls.Config will inherit MinValue and MaxValue
// from the vu state tls.Config
tlsCfg := &tls.Config{
CipherSuites: parentConfig.CipherSuites,
InsecureSkipVerify: parentConfig.InsecureSkipVerify, //nolint:gosec
MinVersion: parentConfig.MinVersion,
MaxVersion: parentConfig.MaxVersion,
Renegotiation: parentConfig.Renegotiation,
RootCAs: cp,
}
if len(certificate) > 0 && len(key) > 0 {
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
return nil, fmt.Errorf("failed to append certificate from PEM: %w", err)
}
tlsCfg.Certificates = []tls.Certificate{cert}
}
return tlsCfg, nil
}
func buildTLSConfigFromMap(parentConfig *tls.Config, tlsConfigMap map[string]any) (*tls.Config, error) {
var cert, key, pass []byte
var ca [][]byte
var err error
if certstr, ok := tlsConfigMap["cert"].(string); ok {
cert = []byte(certstr)
}
if keystr, ok := tlsConfigMap["key"].(string); ok {
key = []byte(keystr)
}
if passwordStr, ok := tlsConfigMap["password"].(string); ok {
pass = []byte(passwordStr)View on GitHub (pinned to 93accf6570)
Solutions
- Verify the pair matches: compare `openssl x509 -noout -modulus` output with `openssl rsa -noout -modulus` (moduli must be identical)
- Re-export clean PEM files and reference them with open()
- For encrypted keys pass tls.password (PKCS#8-encrypted is unsupported - convert with `openssl rsa -in enc.key -out plain.key`)
Example fix
# before: mismatched pair (rotated cert, old key)
connect(addr, { tls: { cert: open('client_v2.crt'), key: open('client_v1.key') } })
# after: matching pair
connect(addr, { tls: { cert: open('client_v2.crt'), key: open('client_v2.key') } }) Defensive patterns
Strategy: validation
Validate before calling
const isPem = (s) => typeof s === 'string' && /-----BEGIN [A-Z0-9 ]+-----/.test(s);
function assertClientTLS(tls) {
if ((tls.cert && !isPem(tls.cert)) || (tls.key && !isPem(tls.key))) {
throw new Error('tls.cert and tls.key must be PEM strings (file contents, not paths)');
}
return tls;
}
client.connect(addr, { tls: assertClientTLS({ cert: open('c.crt'), key: open('c.key') }) }); Try / catch
try { client.connect(addr, { tls }); } catch (e) { if (/failed to append certificate from PEM/.test(e.message)) { /* verify cert/key pair with openssl, fix and retry */ } throw e; } Prevention
- Ship cert and key as one versioned pair; never mix rotations
- Verify pairs offline: compare x509 and rsa modulus output
- Use unencrypted PEM keys, or tls.password for supported encrypted formats
When it happens
Trigger: connect(addr, { tls: { cert: open('client.crt'), key: open('client.key') } }) where the pair mismatches (cert rotated, stale key), either PEM is malformed, or the key is encrypted in an unsupported encoding (encrypted PKCS#8 fails earlier in decryptPrivateKey with its own message).
Common situations: Cert rotation where the new certificate ships but the old key remains; passing the CA bundle as cert; encrypted keys supplied without tls.password; PEMs mangled through env vars.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to append ca certificate [%d] from PEM
- failed to decode PEM key
- failed to append root certificate to the pool
- encrypted pkcs8 formatted key is not supported
- gRPC exporter endpoint is required
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/522d6da6b30e0d4a.
Report an issue: GitHub.