slackhq/nebula · critical

ErrRootExpired

ErrRootExpired

Error message

root certificate is expired

What it means

ErrRootExpired means the signing (root/CA) certificate's NotAfter time has passed relative to the `now` supplied to verify. The chain itself may be structurally valid, but trust is refused because the signer can no longer validate new chains.

Source

Thrown at cert/errors.go:10

package cert

import (
	"errors"
	"fmt"
)

var (
	ErrBadFormat                  = errors.New("bad wire format")
	ErrRootExpired                = errors.New("root certificate is expired")
	ErrExpired                    = errors.New("certificate is expired")
	ErrNotCA                      = errors.New("certificate is not a CA")
	ErrNotSelfSigned              = errors.New("certificate is not self-signed")
	ErrBlockListed                = errors.New("certificate is in the block list")
	ErrFingerprintMismatch        = errors.New("certificate fingerprint did not match")
	ErrSignatureMismatch          = errors.New("certificate signature did not match")
	ErrInvalidPublicKey           = errors.New("invalid public key")
	ErrInvalidPrivateKey          = errors.New("invalid private key")
	ErrPublicPrivateCurveMismatch = errors.New("public key does not match private key curve")
	ErrPublicPrivateKeyMismatch   = errors.New("public key and private key are not a pair")
	ErrPrivateKeyEncrypted        = errors.New("private key must be decrypted")
	ErrCaNotFound                 = errors.New("could not find ca for the certificate")
	ErrUnknownVersion             = errors.New("certificate version unrecognized")
	ErrCertPubkeyPresent          = errors.New("certificate has unexpected pubkey present")
	ErrCurveMismatch              = errors.New("certificate curve does not match CA")

	ErrInvalidPEMBlock                   = errors.New("input did not contain a valid PEM encoded block")
	ErrInvalidPEMCertificateBanner       = errors.New("bytes did not contain a proper certificate banner")

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Issue/rotate a new CA certificate and redistribute the updated ca.crt to all nodes.
  2. Verify the local clock (date/NTP) — skew can make a valid CA appear expired.
  3. Re-sign the leaf certificate with a still-valid CA.
  4. Regenerate test pools with certs whose NotAfter covers the test run time.

Example fix

// before
pool := cert.NewCAPoolFromBytes(caPem) // old expired CA
valid, err := pool.VerifyCertificate(cert)

// after
caPem = fetchRotatedCA() // deploy renewed CA
pool := cert.NewCAPoolFromBytes(caPem)
valid, err := pool.VerifyCertificate(cert)
Defensive patterns

Strategy: try-catch

Validate before calling

now := time.Now()
for _, ca := range pool.CAs {
    if ca.Certificate.Expired(now) {
        log.Warn("CA %s expired at %v — rotate it", ca.Certificate.Name(), now)
    }
}

Type guard

func isSignerUsable(signer *cert.CachedCertificate, now time.Time) bool {
    return signer != nil && !signer.Certificate.Expired(now)
}

Try / catch

s, err := pool.VerifyCertificate(c, fp, "")
if errors.Is(err, cert.ErrRootExpired) {
    return fmt.Errorf("CA expired — deploy a renewed ca.crt (rotation required)")
}

Prevention

When it happens

Trigger: CAPool.verify (cert/ca_pool.go:225) returns this when signer.Certificate.Expired(now) is true — i.e., VerifyCertificate/VerifyCachedCertificate is called with a certificate whose selected CA signer's expiry is before the current time.

Common situations: A long-lived deployment whose CA cert aged past NotAfter without rotation; system clock skew (machine timezone/clock far off) making a still-valid CA look expired; reusing old test fixtures with fixed dates.

Understand the failure class

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/d22cc1812e0f9531. Report an issue: GitHub.