semaphoreui/semaphore · error
jwt: unsupported curve
Error message
jwt: unsupported curve %q, expected P-256
What it means
Thrown by parsePrivateKey in pkg/jwt/signer.go when a PKCS#8 PEM key parses successfully as ECDSA but its elliptic curve is not P-256 (e.g. P-384, P-521, or a custom curve). The JWT signer deliberately requires ES256, which is only defined over P-256, so any other curve is rejected before the key is used for signing.
Solutions
- Regenerate the signing key with an ECDSA P-256 curve (e.g. openssl ecparam -name prime256v1 -genkey -noout) and re-import the PEM
- If the key was provisioned via config or the jwt_signing_key database option, replace that stored key with a P-256 PEM
- Verify with 'openssl ec -in key.pem -text -noout' that the curve reports prime256v1 before deploying
- If you cannot replace the key, disable the JWT feature until a compliant key is available, since the signer will not start
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at pkg/jwt/signer.go:163 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/54656e8e6a8113d2.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/jwt/signer.go:163
set := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{jwk}}
return json.Marshal(set)
}
func parsePrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("jwt: no PEM block found in key file")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
ecKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("jwt: key is not ECDSA (got %T)", key)
}
if ecKey.Curve != elliptic.P256() {
return nil, fmt.Errorf("jwt: unsupported curve %q, expected P-256", ecKey.Curve.Params().Name)
}
return ecKey, nil
}
func computeKID(pub *ecdsa.PublicKey) (string, error) {
der, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return "", err
}
sum := sha256.Sum256(der)
return base64.RawURLEncoding.EncodeToString(sum[:]), nil
}
func randomJTI() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}View on GitHub (pinned to 1774ccb71a)