hashicorp/nomad · error
certificate has expired or is not yet valid
Error message
certificate has expired or is not yet valid
What it means
getCassCert parses a PEM-encoded X.509 certificate and verifies the wall-clock time falls within the certificate's validity window (NotBefore..NotAfter). It returns this error when time.Now() is outside that window, meaning the certificate cannot currently be trusted for signing the OIDC client-assertion private key JWT.
Source
Thrown at lib/auth/oidc/client_assertion.go:184
if k.PemCert != "" {
source = "PemCert"
bts = []byte(k.PemCert)
}
// ensure newlines around pem header/footer
bts = newlineHeaders(bts)
block, _ := pem.Decode(bts)
if block == nil {
return nil, fmt.Errorf("failed to decode %s PEM block", source)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse %s bytes: %w", source, err)
}
now := time.Now()
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
return nil, errors.New("certificate has expired or is not yet valid")
}
return cert, nil
}
// hashKeyID derives a "certificate thumbprint" that the OIDC provider uses
// to find the certificate to verify the private key JWT signature.
// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.7
func hashKeyID(cert *x509.Certificate, header structs.OIDCClientAssertionKeyIDHeader) (string, error) {
var hasher hash.Hash
switch header {
case structs.OIDCClientAssertionHeaderX5t:
if fips140.Enabled() {
return "", errors.New("x5t assertion headers use SHA-1, which is forbidden in FIPS-140 mode")
}
hasher = sha1.New()
case structs.OIDCClientAssertionHeaderX5tS256:
hasher = sha256.New()View on GitHub (pinned to 482b49bf1a)
Solutions
- Renew/reissue the OIDC client signing certificate and update the auth method config with the new PEM.
- Check server clock sync (NTP) — a skewed clock makes valid certs appear expired or not-yet-valid.
- If a new cert was just installed, confirm its NotBefore date has passed and redeploy after activation.
- Use a longer-lived certificate or automate rotation before NotAfter to prevent recurrence.
Example fix
// before (expired cert in config) key_source = "cert" // cert PEM expired 2025-01-01 // after # renew certificate, then update auth method: key_source = "cert" # PEM with NotAfter in the future # or verify time: # openssl x509 -in cert.pem -noout -dates
Defensive patterns
Strategy: validation
Validate before calling
pemBytes, _ := os.ReadFile("client-cert.pem")
blk, _ := pem.Decode(pemBytes)
cert, err := x509.ParseCertificate(blk.Bytes)
if err != nil { return err }
now := time.Now()
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
return fmt.Errorf("OIDC client cert not currently valid: notBefore=%s notAfter=%s", cert.NotBefore, cert.NotAfter)
} Prevention
- Monitor certificate NotAfter and alert well before expiry
- Use NTP on servers to keep clocks accurate
- Validate cert dates at startup of the auth method, not only at request time
- Automate cert rotation before NotAfter
When it happens
Trigger: BuildClientAssertionJWT calls getCassCert with the configured signing certificate PEM while (a) the certificate's NotAfter timestamp has passed, or (b) the certificate's NotBefore is in the future (e.g. clock skew or a not-yet-activated cert).
Common situations: Nomad ACL auth method configured with an OIDC client certificate that expired and nobody rotated it; server clock skew after NTP failure making a valid cert appear expired; a newly issued certificate deployed before its NotBefore date; long-lived certs issued years ago in test environments.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- common name value not provided
- country value not provided
- organization value not provided
- organizational unit value not provided
- failed to parse CA file: %w
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/025854ce90c02138.
Report an issue: GitHub.