semaphoreui/semaphore · error
jwt: could not load or create signing key
Error message
jwt: could not load or create signing key: %w
What it means
Returned by InitJWTSignerFromStore in util/jwt.go when loadOrCreateJWTKey fails at any step — reading the jwt_signing_key option from the database, decrypting the stored PEM, generating a fresh key, or persisting a newly generated one. It is a wrapping guard around the whole key lifecycle: JWT signing is enabled in config, so the service cannot start without a usable signing key, and the %w carries the concrete underlying failure.
Solutions
- Read the wrapped error to see which stage failed: option read (DB), decrypt (keyring), generate (crypto/rand), or persist (DB write)
- Verify the configured encryption key is correct — a rotated or wrong key makes the stored jwt_signing_key undecryptable
- If the stored key is unrecoverable, use the rekey/vault flow (RekeyJWTSigningKey) to install a fresh signing key
- Check database connectivity/permissions if the option read or write itself failed
Defensive patterns
Strategy: fallback
When it happens
Trigger: Thrown at util/jwt.go:34 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/216fab218d210e5e.
Report an issue: GitHub.
Appendix: source
Thrown at util/jwt.go:34
SetOption(key string, value string) error
}
// jwtSigningKeyOption is the database option key under which the AES-GCM
// encrypted ECDSA P-256 private key PEM is stored.
const jwtSigningKeyOption = "jwt_signing_key"
// InitJWTSignerFromStore initialises the global JWT signer.
// It must be called once after the db.Store has been opened and after ConfigInit has run.
func InitJWTSignerFromStore(store OptionStore) (singer jwt.Signer, err error) {
if !Config.JWT.Enabled {
return
}
opts := jwtSignerOptions()
pemBytes, err := loadOrCreateJWTKey(store)
if err != nil {
return nil, fmt.Errorf("jwt: could not load or create signing key: %w", err)
}
signer, err := jwt.NewECDSASignerFromPEM(pemBytes, opts)
if err != nil {
return nil, fmt.Errorf("jwt: failed to initialise signer: %w", err)
}
return signer, nil
}
// jwtSignerOptions builds SignerOptions from the current Config.
func jwtSignerOptions() jwt.SignerOptions {
ttl := time.Hour
if Config.JWT.DefaultTTL != "" {
if parsed, err := time.ParseDuration(Config.JWT.DefaultTTL); err == nil {
ttl = parsed
} else {
fmt.Fprintf(os.Stderr, "jwt: invalid jwt_default_ttl %q, falling back to 1h: %v\n", Config.JWT.DefaultTTL, err)View on GitHub (pinned to 1774ccb71a)