semaphoreui/semaphore · error
jwt: failed to initialise signer
Error message
jwt: failed to initialise signer: %w
What it means
Returned by InitJWTSignerFromStore in util/jwt.go when a signing-key PEM was obtained (loaded or newly generated) but jwt.NewECDSASignerFromPEM rejects it — for example the PEM block is missing, the key is not ECDSA, or the curve is not P-256 (the signer's own parsePrivateKey errors). The %w wraps the signer construction error, distinguishing a bad key from the key-loading failure handled one branch above.
Solutions
- Inspect the wrapped signer error — 'no PEM block', 'key is not ECDSA', or 'unsupported curve' each point to a different fix
- Regenerate the stored key as an ECDSA P-256 PEM and update the jwt_signing_key option (or use the rekey flow)
- If the stored value was manually edited, restore the exact PEM produced by the key-generation path
- Confirm JWT config options (issuer/TTLs) parse correctly so the signer options themselves are not the cause
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at util/jwt.go:39 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/70d9ec971ceb0fbd.
Report an issue: GitHub.
Appendix: source
Thrown at util/jwt.go:39
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)
}
}
maxTTL := 24 * time.Hour
if Config.JWT.MaxTTL != "" {View on GitHub (pinned to 1774ccb71a)