golang/go · warning
client doesn't support ECDHE, can only use legacy RSA key ex
Error message
client doesn't support ECDHE, can only use legacy RSA key exchange
What it means
Thrown by SupportsCertificate when the client does not support ECDHE key exchange (no common elliptic curve or point format), and the fallback static-RSA check is wrapped around this error. ECDHE is the only signed key exchange Go supports; without it the connection can only use legacy static RSA. The actual return is supportsRSAFallback(this error), so callers may see nil if static RSA is viable.
Source
Thrown at src/crypto/tls/common.go:1473
if _, err := selectSignatureScheme(vers, c, chi.SignatureSchemes); err != nil {
return supportsRSAFallback(err)
}
}
// In TLS 1.3 we are done because supported_groups is only relevant to the
// ECDHE computation, point format negotiation is removed, cipher suites are
// only relevant to the AEAD choice, and static RSA does not exist.
if vers == VersionTLS13 {
return nil
}
// The only signed key exchange we support is ECDHE.
ecdheSupported, err := supportsECDHE(config, vers, chi.SupportedCurves, chi.SupportedPoints)
if err != nil {
return err
}
if !ecdheSupported {
return supportsRSAFallback(errors.New("client doesn't support ECDHE, can only use legacy RSA key exchange"))
}
var ecdsaCipherSuite bool
if priv, ok := c.PrivateKey.(crypto.Signer); ok {
switch pub := priv.Public().(type) {
case *ecdsa.PublicKey:
var curve CurveID
switch pub.Curve {
case elliptic.P256():
curve = CurveP256
case elliptic.P384():
curve = CurveP384
case elliptic.P521():
curve = CurveP521
default:
return supportsRSAFallback(unsupportedCertificateError(c))
}
var curveOk boolView on GitHub (pinned to b6b368adc5)
Solutions
- Add common curves (X25519, P-256) to the server's Config.CurvePreferences and the client's SupportedCurves.
- Upgrade the client to a TLS stack that advertises ECDHE curves.
- If legacy support is required, allow an RSA cipher suite and TLS <=1.2 so static RSA fallback works.
- Avoid disabling all curves in CurvePreferences.
Example fix
// before
cfg.CurvePreferences = []tls.CurveID{tls.CurveP521} // client has only X25519
// after
cfg.CurvePreferences = []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP521} Defensive patterns
Strategy: validation
Validate before calling
// Verify ECDHE is achievable for a ClientHello before committing to an ECDSA cert.
func supportsModernECDHE(chi *tls.ClientHelloInfo) bool {
for _, c := range chi.SupportedCurves {
if c == tls.X25519 || c == tls.CurveP256 { return true }
}
return false
} Prevention
- Keep X25519 and P-256 in CurvePreferences; they have the widest client support.
- Avoid disabling all curves in server config.
- If supporting legacy clients, allow an RSA cipher suite at TLS <=1.2 as fallback.
- Survey client SupportedCurves before restricting CurvePreferences.
When it happens
Trigger: SupportsCertificate on a cert where supportsECDHE returns false: the client's SupportedCurves/SupportedPoints share nothing with the server's allowed curves. The error is returned only if static-RSA fallback is also unsupported (TLS 1.3, non-RSA key, or no mutual RSA cipher suite).
Common situations: Legacy client advertising no modern curves; server restricted CurvePreferences to curves the client lacks; TLS 1.3 (no static RSA) combined with a curve-less client; hardening that removed common curves.
Related errors
- tls: no key exchanges supported by both client and server
- tls: invalid client key share
- ECDSA verification failure
- Ed25519 verification failure
- tls: missing signature_algorithms from TLS 1.2 peer
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/585d8b7cf7e2c719.
Report an issue: GitHub.