golang/go · error
tls: no supported key exchange methods (CurveIDs)
Error message
tls: no supported key exchange methods (CurveIDs)
What it means
Thrown by makeClientHello when TLS 1.3 is being negotiated (maxVersion >= VersionTLS13) but hello.supportedCurves is empty after filtering. The supportedCurves list is derived from config.curvePreferences(maxVersion), which filters the library's known curve set by the config's CurvePreferences and FIPS constraints. An empty result means no usable key exchange curve is available for TLS 1.3, which requires at least one named group for key exchange.
Source
Thrown at src/crypto/tls/handshake_client.go:143
}
var keyShareKeys *keySharePrivateKeys
if maxVersion >= VersionTLS13 {
// Reset the list of ciphers when the client only supports TLS 1.3.
if minVersion >= VersionTLS13 {
hello.cipherSuites = nil
}
if fips140tls.Required() {
hello.cipherSuites = append(hello.cipherSuites, allowedCipherSuitesTLS13FIPS...)
} else if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
} else {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
}
if len(hello.supportedCurves) == 0 {
return nil, nil, nil, errors.New("tls: no supported key exchange methods (CurveIDs)")
}
// Since the order is fixed, the first one is always the one to send a
// key share for. All the PQ hybrids sort first, and produce a fallback
// ECDH share.
curveID := hello.supportedCurves[0]
ke, err := keyExchangeForCurveID(curveID)
if err != nil {
return nil, nil, nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve")
}
keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand())
if err != nil {
return nil, nil, nil, err
}
// Only send the fallback ECDH share if the corresponding CurveID is enabled.
if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) {
hello.keyShares = hello.keyShares[:1]
}
}View on GitHub (pinned to b6b368adc5)
Solutions
- Include at least one standard, library-supported curve in CurvePreferences (e.g., tls.X25519, tls.CurveP256, tls.CurveP384)
- Leave CurvePreferences unset (nil) to use the library default curve set
- If running in FIPS mode, ensure CurvePreferences includes at least one FIPS-approved curve (P-256, P-384)
- Check GODEBUG settings that might disable specific curves at runtime
Example fix
// before — only unsupported curves
config := &tls.Config{
CurvePreferences: []tls.CurveID{999, 998}, // unknown curve IDs
}
// after
config := &tls.Config{
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384},
}
// or omit entirely:
config := &tls.Config{} Defensive patterns
Strategy: validation
Validate before calling
func validateCurvePreferences(config *tls.Config) error {
if len(config.CurvePreferences) == 0 {
return nil // nil/empty uses library defaults — always valid
}
knownCurves := []tls.CurveID{
tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521,
}
hasKnown := false
for _, c := range config.CurvePreferences {
for _, k := range knownCurves {
if c == k {
hasKnown = true
break
}
}
}
if !hasKnown {
return errors.New("CurvePreferences contains no library-supported curves")
}
return nil
} Type guard
// Check if a CurveID is a standard supported curve
func isStandardCurve(c tls.CurveID) bool {
switch c {
case tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521:
return true
}
return false
} Try / catch
// Pre-validate before dial:
//
// if err := validateCurvePreferences(config); err != nil {
// config.CurvePreferences = nil // reset to defaults
// } Prevention
- Leave CurvePreferences unset (nil) to use library defaults
- Only include standard, well-known curves (X25519, P-256, P-384) in CurvePreferences
- For FIPS mode, ensure at least one FIPS-approved curve (P-256, P-384) is configured
- Avoid setting CurvePreferences to experimental or unknown curve IDs
When it happens
Trigger: Setting config.CurvePreferences to a non-empty slice containing only curve IDs that are unknown to the library (all filtered out by supportsCurve). Enabling FIPS mode (fips140tls.Required()) while CurvePreferences contains only curves not in the FIPS-allowed set. A GODEBUG setting that disables all default curves.
Common situations: Setting CurvePreferences to experimental or unsupported curve IDs. FIPS mode filtering out all configured curves (e.g., X25519 may not be FIPS-approved depending on the module). A GODEBUG flag like x25519mul=0 or similar that disables a curve at runtime, combined with a CurvePreferences list that only includes that curve.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: MinVersion must be >= VersionTLS13 if EncryptedClientHe
- tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHe
- tls: server selected unsupported curve
- tls: either ServerName or InsecureSkipVerify must be specifi
- tls: invalid NextProtos value
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/276effeebf1c5d93.
Report an issue: GitHub.