grafana/k6 · error
failed to append ca certificate [%d] from PEM
Error message
failed to append ca certificate [%d] from PEM
What it means
During grpc.connect() TLS setup, every entry of tls.cacerts (or the single-string form) is passed to x509.CertPool.AppendCertsFromPEM; when an entry yields no parsable CERTIFICATE block, buildTLSConfig fails with 'failed to append ca certificate [<index>] from PEM' (internal/js/modules/k6/grpc/client.go:157). The index is the position in the cacerts array, identifying exactly which entry is broken. Values must be the PEM content itself, never a file path.
Source
Thrown at internal/js/modules/k6/grpc/client.go:157
*/
decryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck
if err != nil {
return nil, err
}
key = pem.EncodeToMemory(&pem.Block{
Type: blockType,
Bytes: decryptedKey,
})
return key, nil
}
func buildTLSConfig(parentConfig *tls.Config, certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) {
var cp *x509.CertPool
if len(caCertificates) > 0 {
cp, _ = x509.SystemCertPool()
for i, caCert := range caCertificates {
if ok := cp.AppendCertsFromPEM(caCert); !ok {
return nil, fmt.Errorf("failed to append ca certificate [%d] from PEM", i)
}
}
}
// 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 {View on GitHub (pinned to 93accf6570)
Solutions
- Fix the entry indicated by the index: provide full PEM text including the -----BEGIN CERTIFICATE----- / -----END CERTIFICATE----- lines
- Load PEMs from files with open() rather than env vars, or restore real newlines (str.split('\\n').join('\n') style repair)
- Validate each cert with a PEM-shape check before calling connect (see defense)
Example fix
// before (env var lost the newlines)
connect(addr, { tls: { cacerts: [__ENV.CA_PEM] } })
// after (read the PEM file directly)
connect(addr, { tls: { cacerts: [open('ca.pem')] } }) Defensive patterns
Strategy: validation
Validate before calling
const PEM_CERT = /-----BEGIN CERTIFICATE-----[\s\S]+-----END CERTIFICATE-----[\s]*$/;
function assertCaCerts(cacerts) {
const list = Array.isArray(cacerts) ? cacerts : [cacerts];
list.forEach((c, i) => {
if (!PEM_CERT.test(c)) throw new Error(`cacerts[${i}] is not a valid PEM certificate`);
});
return cacerts;
}
connect(addr, { tls: { cacerts: assertCaCerts([open('ca.pem'), __ENV.EXTRA_CA]) } }); Type guard
function isPemCert(v) {
return typeof v === 'string' && /-----BEGIN CERTIFICATE-----/.test(v) && /-----END CERTIFICATE-----/.test(v);
} Try / catch
try { client.connect(addr, params); } catch (e) { if (/failed to append ca certificate \[(\d+)\]/.test(e.message)) { /* fix that cacerts entry, reload from file, retry */ } throw e; } Prevention
- Load CA PEMs from files via open(), not env vars
- If PEMs travel through env/CI secrets, re-expand escaped newlines before use
- The index in the message points at the exact broken array entry
When it happens
Trigger: connect(addr, { tls: { cacerts: [open('ca.pem'), __ENV.EXTRA_CA] } }) where any entry is not valid PEM: a file path passed instead of contents, newlines stripped by env vars/CI secrets, cert+key concatenated into cacerts, or a mangled Base64 body.
Common situations: Loading CA material from environment variables or secret managers that collapse '\n'; pasting single-line PEMs from browsers; mixing up the cacerts and cert/key options.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to decode PEM key
- failed to append root certificate to the pool
- failed to append certificate from PEM: %w
- 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/1bbf1f3e2cc068ab.
Report an issue: GitHub.