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 *keyShare

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Widen tls.Config.CurvePreferences to include widely-supported groups (X25519, CurveP256, CurveP384)
  2. If using defaults, the client offered zero recognized curves — update the client
  3. 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

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

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/15caea5cb64e3eb8. Report an issue: GitHub.