golang/go · error
tls: server sent an unnecessary HelloRetryRequest message
Error message
tls: server sent an unnecessary HelloRetryRequest message
What it means
Thrown during HelloRetryRequest processing when the HRR contains neither a selected_group (in key_share) nor a cookie. RFC 8446 section 4.1.4 requires that a HelloRetryRequest must result in a change to the ClientHello — an HRR with no actionable content is unnecessary and invalid.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:297
c.echAccepted = true
}
}
if err := transcriptMsg(hs.serverHello, hs.echContext.innerTranscript); err != nil {
return err
}
} else if hs.serverHello.encryptedClientHello != nil {
// Unsolicited ECH extension should be rejected
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: unexpected encrypted client hello extension in serverHello")
}
// The only HelloRetryRequest extensions we support are key_share and
// cookie, and clients must abort the handshake if the HRR would not result
// in any change in the ClientHello.
if hs.serverHello.selectedGroup == 0 && hs.serverHello.cookie == nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server sent an unnecessary HelloRetryRequest message")
}
if hs.serverHello.cookie != nil {
hello.cookie = hs.serverHello.cookie
}
if hs.serverHello.serverShare.group != 0 {
c.sendAlert(alertDecodeError)
return errors.New("tls: received malformed key_share extension")
}
// If the server sent a key_share extension selecting a group, ensure it's
// a group we advertised but did not send a key share for, and send a key
// share for it this time.
if curveID := hs.serverHello.selectedGroup; curveID != 0 {
if !slices.Contains(hello.supportedCurves, curveID) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server selected unsupported group")View on GitHub (pinned to b6b368adc5)
Solutions
- Server must include either a key_share selected_group or a cookie in the HelloRetryRequest.
- Report as a server-side protocol violation per RFC 8446 section 4.1.4.
- If the server cannot be fixed, restrict client to TLS 1.2 to avoid HRR entirely.
- Capture the handshake with Wireshark to confirm the HRR contents.
Defensive patterns
Strategy: try-catch
Try / catch
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
if strings.Contains(err.Error(), "unnecessary HelloRetryRequest") {
// Server sends pointless HRR — fall back to TLS 1.2
config.MaxVersion = tls.VersionTLS12
conn, err = tls.Dial("tcp", addr, config)
}
} Prevention
- Verify server TLS 1.3 HRR implementation with Wireshark.
- Report unnecessary HRR bugs to server vendors.
- Keep a TLS 1.2 fallback for non-compliant servers.
When it happens
Trigger: Triggered when both hs.serverHello.selectedGroup == 0 and hs.serverHello.cookie == nil. The client sends alertIllegalParameter. The HRR would not cause any change in the next ClientHello.
Common situations: Server bug sending a pointless HelloRetryRequest with no key_share or cookie extensions. Server using HRR for hello random manipulation without including required actionable extensions. Non-compliant TLS 1.3 server implementation. Server sending HRR to work around a bug in its own state machine.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: server selected TLS 1.3 using the legacy version field
- tls: server selected an invalid version after a HelloRetryRe
- tls: server sent an incorrect legacy version
- tls: server sent a ServerHello extension forbidden in TLS 1.
- tls: server did not echo the legacy session ID
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/62774220d9abb3be.
Report an issue: GitHub.