grafana/k6 · error

failed to append root certificate to the pool

Error message

failed to append root certificate to the pool

What it means

When K6_OTEL_TLS_CERTIFICATE points at a root CA for the OTel exporter's TLS connection, k6 reads the file and feeds it to x509.CertPool.AppendCertsFromPEM. If the file was read successfully but does not contain parseable PEM certificate blocks (e.g., it is DER-encoded binary, a malformed bundle, or not a certificate at all), the append fails and the output returns this error.

Solutions

  1. Convert DER to PEM: openssl x509 -inform der -in ca.cer -out ca.pem and point K6_OTEL_TLS_CERTIFICATE at ca.pem
  2. Verify the file first: openssl x509 -in ca.pem -noout -subject (or check it starts with '-----BEGIN CERTIFICATE-----')
  3. Re-download/export the CA bundle from the source that issued it instead of hand-editing

Example fix

# before
export K6_OTEL_TLS_CERTIFICATE=/etc/certs/ca.cer   # DER-encoded
k6 run -o opentelemetry script.js
# after
openssl x509 -inform der -in /etc/certs/ca.cer -out /etc/certs/ca.pem
export K6_OTEL_TLS_CERTIFICATE=/etc/certs/ca.pem
k6 run -o opentelemetry script.js
Defensive patterns

Strategy: validation

Validate before calling

// Shell: verify PEM before the run
ca="${K6_OTEL_TLS_CERTIFICATE:-}"
if [ -n "$ca" ]; then
  head -1 "$ca" | grep -q '^-----BEGIN CERTIFICATE-----' \
    || { echo "$ca is not PEM; convert with: openssl x509 -inform der -in $ca -out $ca.pem" >&2; exit 1; }
fi
k6 run -o opentelemetry script.js

Type guard

// Go
func isPEMCert(b []byte) bool {
    block, _ := pem.Decode(b)
    return block != nil && block.Type == "CERTIFICATE"
}

Prevention

When it happens

Trigger: K6_OTEL_TLS_CERTIFICATE pointing at a DER/binary .crt file (common for Windows-exported CAs); a truncated or concatenated-with-garbage PEM bundle; a proxy that saved an HTML error page as ca.pem.

Common situations: Corporate proxy/CA setups where the provided CA file is DER, not PEM; manual copy-paste of certs that lost the BEGIN/END lines; expired-then-regenerated bundles with stray bytes.

Understand the failure class

Related errors


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/76979d8cd93801dc. Report an issue: GitHub.

Appendix: source

Thrown at internal/output/opentelemetry/tls.go:36

	tlsConfig := &tls.Config{
		MinVersion: tls.VersionTLS13,
	}

	if insecureSkipVerify.Valid {
		tlsConfig.InsecureSkipVerify = insecureSkipVerify.Bool
		set = true
	}

	// Load the root certificate
	if certPath.Valid {
		b, err := os.ReadFile(certPath.String) //nolint:forbidigo
		if err != nil {
			return nil, fmt.Errorf("failed to read root certificate from %q: %w", certPath.String, err)
		}

		cp := x509.NewCertPool()
		if ok := cp.AppendCertsFromPEM(b); !ok {
			return nil, errors.New("failed to append root certificate to the pool")
		}

		tlsConfig.RootCAs = cp
		set = true
	}

	// Load the client certificate
	if clientCertPath.Valid {
		cert, err := tls.LoadX509KeyPair(clientCertPath.String, clientKeyPath.String)
		if err != nil {
			return nil, fmt.Errorf("failed to load client certificate: %w", err)
		}

		tlsConfig.Certificates = []tls.Certificate{cert}
		set = true
	}

	if !set {

View on GitHub (pinned to 01ffac6f24)