golang/go · error
tls: no key exchanges supported by both client and server
Error message
tls: no key exchanges supported by both client and server
What it means
After intersecting the server's curvePreferences with the client's supported_groups (hs.clientHello.supportedCurves), the result is empty — no (EC)DHE group is mutually supported. Per RFC 8446 §4.2.7 the server sends handshake_failure and aborts.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:215
c.cipherSuite = hs.suite.id
hs.hello.cipherSuite = hs.suite.id
hs.transcript = hs.suite.hash.New()
// First, if a post-quantum key exchange is available, use one. See
// draft-ietf-tls-key-share-prediction-01, Section 4 for why this must be
// first.
//
// Second, if the client sent a key share for a group we support, use that,
// to avoid a HelloRetryRequest round-trip.
//
// Finally, pick in our fixed preference order.
preferredGroups := c.config.curvePreferences(c.vers)
preferredGroups = slices.DeleteFunc(preferredGroups, func(group CurveID) bool {
return !slices.Contains(hs.clientHello.supportedCurves, group)
})
if len(preferredGroups) == 0 {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: no key exchanges supported by both client and server")
}
hasKeyShare := func(group CurveID) bool {
for _, ks := range hs.clientHello.keyShares {
if ks.group == group {
return true
}
}
return false
}
sort.SliceStable(preferredGroups, func(i, j int) bool {
return hasKeyShare(preferredGroups[i]) && !hasKeyShare(preferredGroups[j])
})
sort.SliceStable(preferredGroups, func(i, j int) bool {
return isPQKeyExchange(preferredGroups[i]) && !isPQKeyExchange(preferredGroups[j])
})
selectedGroup := preferredGroups[0]
var clientKeyShare *keyShareView on GitHub (pinned to b6b368adc5)
Solutions
- Widen tls.Config.CurvePreferences to include widely-supported groups (X25519, CurveP256, CurveP384)
- If using defaults, the client offered zero recognized curves — update the client
- For FIPS deployments, ensure the client offers at least one FIPS-approved group (P-256 or P-384)
Example fix
// before (too narrow)
cfg := &tls.Config{CurvePreferences: []tls.CurveID{tls.CurveP521}}
// after (broad interop)
cfg := &tls.Config{CurvePreferences: []tls.CurveID{
tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521,
}} Defensive patterns
Strategy: validation
Validate before calling
// Validate curve overlap before listening, against the groups you expect clients to offer.
commonCurves := []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384}
if len(cfg.CurvePreferences) > 0 {
ok := false
for _, g := range commonCurves {
for _, c := range cfg.CurvePreferences {
if g == c { ok = true }
}
}
if !ok {
log.Printf("WARN: CurvePreferences %v may not intersect common client groups %v", cfg.CurvePreferences, commonCurves)
}
} Try / catch
if err := tlsConn.Handshake(); err != nil {
if strings.Contains(err.Error(), "no key exchanges supported") {
log.Printf("no common group with client %v; check CurvePreferences", remote)
}
c.Close()
return
} Prevention
- Keep CurvePreferences broad (X25519, P-256, P-384) for interop unless you have a hardening reason
- In FIPS mode, document that clients must offer P-256 or P-384
- Audit load balancers for supported_groups stripping
When it happens
Trigger: Server's tls.Config.CurvePreferences filtered against the client's supportedCurves yields zero groups. E.g., server configured CurvePreferences = [X25519] but client only offers P-256; or vice-versa.
Common situations: Hardened/locked-down CurvePreferences; FIPS-only server configs (P-256/P-384) talking to clients that only offer modern curves; misconfigured load balancers that strip supported_groups; very old or very constrained clients.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client doesn't support ECDHE, can only use legacy RSA key ex
- tls: invalid client key share
- tls: server selected unoffered curve
- tls: missing signature_algorithms from TLS 1.2 peer
- tls: peer doesn't support any of the certificate's signature
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/15caea5cb64e3eb8.
Report an issue: GitHub.