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
- Issue/rotate a new CA certificate and redistribute the updated ca.crt to all nodes.
- Verify the local clock (date/NTP) — skew can make a valid CA appear expired.
- Re-sign the leaf certificate with a still-valid CA.
- 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
- Monitor CA NotAfter dates and rotate well before expiry.
- Run NTP/clock sync on all nodes.
- Alert on pool.CAs entries close to expiry during startup checks.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/d22cc1812e0f9531.
Report an issue: GitHub.