cilium/cilium · error

PEM format error in TLS Key

Error message

PEM format error in TLS Key

What it means

validateTLSSecret checks that both the TLS certificate and private key stored in a Kubernetes TLS-type Secret are valid PEM-encoded data before the Gateway listener uses them. This error means the Secret's tls.key entry failed helpers.IsValidPemFormat — it is empty, malformed, or not PEM armored. The controller rejects the listener so it is not programmed with an unusable key.

Source

Thrown at operator/pkg/gateway-api/status_listener.go:672

	return res
}

func (m *ListenerStatusManager) validateTLSSecret(ctx context.Context, namespace, name string) error {
	secret := &corev1.Secret{}
	if err := m.client.Get(ctx, client.ObjectKey{
		Namespace: namespace,
		Name:      name,
	}, secret); err != nil {
		return err
	}

	if !helpers.IsValidPemFormat(secret.Data[corev1.TLSCertKey]) {
		return fmt.Errorf("PEM format error in TLS Certificate")
	}

	if !helpers.IsValidPemFormat(secret.Data[corev1.TLSPrivateKeyKey]) {
		return fmt.Errorf("PEM format error in TLS Key")
	}
	return nil
}

func (m *ListenerStatusManager) filterOutInvalidListeners(ctx context.Context, listeners []ingestion.ListenerWithContext, grants []gatewayv1.ReferenceGrant) ([]ingestion.ListenerWithContext, []ingestion.ListenerWithContext) {
	valid := make([]ingestion.ListenerWithContext, 0, len(listeners))
	invalid := make([]ingestion.ListenerWithContext, 0, len(listeners))
	for _, listener := range listeners {
		res := m.validateListener(ctx, listener.Listener, listenerValidationParams{
			ownerNamespace: listener.Source.Namespace,
			ownerKind:      listener.Source.Kind,
			generation:     listener.SourceGeneration,
			grants:         grants,
			ownerRef: types.NamespacedName{
				Name:      listener.Source.Name,
				Namespace: listener.Source.Namespace,
			}.String(),
		})

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Fix the Secret's tls.key field to contain a complete PEM block including -----BEGIN ... PRIVATE KEY----- and -----END ... PRIVATE KEY----- lines
  2. Recreate the TLS Secret with kubectl create secret tls <name> --cert=cert.pem --key=key.pem
  3. Verify the certificateRefs on the Gateway listener point to the correct Secret name and namespace
  4. If using cert-manager, check the Certificate resource status for issuance errors and let it re-issue the key pair

Example fix

// before: key stored without PEM armor
data:
  tls.key: MIIJKQIBAAKC...   # raw DER, no headers
// after
data:
  tls.key: <base64 of full PEM:
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY----->
Defensive patterns

Strategy: validation

Validate before calling

// Before referencing the Secret in a Gateway listener
for _, k := range []string{"tls.crt", "tls.key"} {
    data, ok := secret.Data[k]
    if !ok || !bytes.Contains(data, []byte("-----BEGIN ")) {
        return fmt.Errorf("secret %s/%s: %s is not PEM encoded", secret.Namespace, secret.Name, k)
    }
}

Type guard

func hasPEMBlock(data []byte) bool {
    block, _ := pem.Decode(data)
    return block != nil
}

Prevention

When it happens

Trigger: A Secret of type kubernetes.io/tls referenced by a Gateway listener's certificateRefs has a tls.key value that is empty, base64 garbage, truncated, or lacks PEM '-----BEGIN ... PRIVATE KEY-----' markers.

Common situations: Secrets created by cert-manager or manually with a raw key pasted without headers; keys converted to PKCS#8/PKCS#1 without armor; typo of tls.key field name; Secret synced from another namespace without the key portion; whitespace/encoding corruption in GitOps pipelines.

Understand the failure class

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/1b5e8cf47d5fe5c6. Report an issue: GitHub.