grafana/k6 · error

invalid tls key value: '%#v', it needs to be a PEM formatted

Error message

invalid tls key value: '%#v', it needs to be a PEM formatted string

What it means

Thrown by k6's gRPC Client.connect() when the tls.key sub-option is present but not a string. parseConnectTLSParam (internal/js/modules/k6/grpc/params.go:233-236) requires the client private key to be a PEM-formatted string. As with cert, the message interpolates the whole tls map (v) rather than just the key value.

Source

Thrown at internal/js/modules/k6/grpc/params.go:235

	return result, nil
}

func parseConnectTLSParam(params *connectParams, v any) error {
	var ok bool
	params.TLS, ok = v.(map[string]any)

	if !ok {
		return fmt.Errorf("invalid tls value: '%#v', expected (optional) keys: cert, key, password, and cacerts", v)
	}
	// optional map keys below
	if cert, certok := params.TLS["cert"]; certok {
		if _, ok = cert.(string); !ok {
			return fmt.Errorf("invalid tls cert value: '%#v', it needs to be a PEM formatted string", v)
		}
	}
	if key, keyok := params.TLS["key"]; keyok {
		if _, ok = key.(string); !ok {
			return fmt.Errorf("invalid tls key value: '%#v', it needs to be a PEM formatted string", v)
		}
	}
	if pass, passok := params.TLS["password"]; passok {
		if _, ok = pass.(string); !ok {
			return fmt.Errorf("invalid tls password value: '%#v', it needs to be a string", v)
		}
	}
	if cacerts, cacertsok := params.TLS["cacerts"]; cacertsok {
		var cacertsArray []any
		if cacertsArray, ok = cacerts.([]any); ok {
			for _, cacertsArrayEntry := range cacertsArray {
				if _, ok = cacertsArrayEntry.(string); !ok {
					return fmt.Errorf("invalid tls cacerts value: '%#v',"+
						" it needs to be a string or an array of PEM formatted strings", v)
				}
			}
		} else if _, ok = cacerts.(string); !ok {
			return fmt.Errorf("invalid tls cacerts value: '%#v',"+

View on GitHub (pinned to 93accf6570)

Solutions

  1. Make tls.key a single PEM string containing '-----BEGIN ... PRIVATE KEY-----'.
  2. Pair it with tls.cert — cert without key (or vice versa) will fail later at TLS setup even if both pass this check.
  3. If the key is passphrase-protected, also provide tls.password as a string.
  4. Check the tls.key entry's type with typeof in your script before connecting.

Example fix

// before
client.connect('host:443', { tls: { cert: certPem, key: { pem: keyPem } } });

// after
client.connect('host:443', { tls: { cert: certPem, key: keyPem } });
Defensive patterns

Strategy: validation

Validate before calling

function validateTls(tls = {}) {
  if ('key' in tls && typeof tls.key !== 'string') throw new Error('tls.key must be a PEM string');
}

Type guard

const isPemString = (v) => typeof v === 'string' && /-----BEGIN [^-]+-----/.test(v);

Prevention

When it happens

Trigger: tls: { key: 42 }, key: { ... }, key: [...], or any non-string value under tls.key. The type check is on the string type only; actual PEM parsing happens later during connection.

Common situations: Passing the key as an object or array; reading the key with a helper that returns an object; mismatching cert (string) and key (non-string) forms in the same tls object.

Understand the failure class

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/1fae4c7b9cfda6a3. Report an issue: GitHub.