k3s-io/k3s · error

new CA bundle contains only a single certificate but should

Error message

new CA bundle contains only a single certificate but should include root or intermediate CA certificates

What it means

During CA rotation (caCertReplace in pkg/server/handlers/cert.go), k3s loads the submitted new CA bundle and rejects it if it contains exactly one certificate. The rotation logic rebuilds root and intermediate pools and verifies the serving/requesting certs against the combined old+new chains, so a single-cert bundle cannot express the root-plus-intermediate (or old-plus-new) chain it needs.

Source

Thrown at pkg/server/handlers/cert.go:189

func validateCA(oldCAPath, newCAPath string) error {
	// Skip validation if old values are being reused
	if oldCAPath == newCAPath {
		return nil
	}

	oldCerts, err := certutil.CertsFromFile(oldCAPath)
	if err != nil {
		return err
	}

	newCerts, err := certutil.CertsFromFile(newCAPath)
	if err != nil {
		return err
	}

	if len(newCerts) == 1 {
		return errors.New("new CA bundle contains only a single certificate but should include root or intermediate CA certificates")
	}

	roots := x509.NewCertPool()
	intermediates := x509.NewCertPool()

	// Load all certs from the old bundle
	for _, cert := range oldCerts {
		if len(cert.AuthorityKeyId) == 0 || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) {
			roots.AddCert(cert)
		} else {
			intermediates.AddCert(cert)
		}
	}

	// Include any intermediates from the new bundle, in case they're cross-signed by a cert in the old bundle
	for i, cert := range newCerts {
		if i > 0 {
			if len(cert.AuthorityKeyId) > 0 {

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Build a full chain bundle: concatenate the new root plus any intermediates (and keep the old root during transition) into one PEM file, e.g. 'cat old-root.crt new-intermediate.crt new-root.crt > bundle.crt'.
  2. Verify the bundle before submitting: 'grep -c "BEGIN CERTIFICATE" bundle.crt' must be greater than 1, and 'openssl crl2pkcs7 -nocrl -certfile bundle.crt | openssl pkcs7 -print_certs' shows all chain members.
  3. Re-run rotate-ca with the corrected --cacert file.

Example fix

# before: single cert -> error
cat new-root.crt > bundle.crt
k3s certificate rotate-ca --cacert=bundle.crt ...

# after: chain bundle (intermediate + root, old root kept during rotation)
cat old-root.crt new-intermediate.crt new-root.crt > bundle.crt
k3s certificate rotate-ca --cacert=bundle.crt --cakey=new-root.key
Defensive patterns

Strategy: validation

Validate before calling

// Count certs in the bundle before submitting rotate-ca
func bundleCertCount(pemPath string) (int, error) {
    b, err := os.ReadFile(pemPath)
    if err != nil {
        return 0, err
    }
    return bytes.Count(b, []byte("BEGIN CERTIFICATE")), nil
}
// require count > 1

Try / catch

if err != nil && strings.Contains(err.Error(), "single certificate") {
    // rebuild bundle as root+intermediates chain and resubmit
}

Prevention

When it happens

Trigger: 'k3s certificate rotate-ca' (HTTP PUT /v1-k3s/cacert) with a --cacert file containing only a leaf-equivalent single CA cert - e.g. just the new root, or just an intermediate, without the rest of the chain.

Common situations: Bundles assembled from a single PEM block; copy/paste of only the intermediate from a provider; chain files truncated during transfer.

Understand the failure class

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/2372a2a4e56d5bb7. Report an issue: GitHub.