golang/go · error
tls: server changed cipher suite after a HelloRetryRequest
Error message
tls: server changed cipher suite after a HelloRetryRequest
What it means
Thrown in checkServerHelloOrHRR() when the server selects a different cipher suite in the final ServerHello than it did in the HelloRetryRequest. RFC 8446 section 4.1.4 requires the cipher suite to be consistent between the HRR and the final ServerHello.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:207
len(hs.serverHello.scts) != 0 {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: server sent a ServerHello extension forbidden in TLS 1.3")
}
if !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server did not echo the legacy session ID")
}
if hs.serverHello.compressionMethod != compressionNone {
c.sendAlert(alertDecodeError)
return errors.New("tls: server sent non-zero legacy TLS compression method")
}
selectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite)
if hs.suite != nil && selectedSuite != hs.suite {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server changed cipher suite after a HelloRetryRequest")
}
if selectedSuite == nil {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: server chose an unconfigured cipher suite")
}
hs.suite = selectedSuite
c.cipherSuite = hs.suite.id
return nil
}
// sendDummyChangeCipherSpec sends a ChangeCipherSpec record for compatibility
// with middleboxes that didn't implement TLS correctly. See RFC 8446, Appendix D.4.
func (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
if hs.c.quic != nil {
return nil
}
if hs.sentDummyCCS {View on GitHub (pinned to b6b368adc5)
Solutions
- Server must keep the same cipher suite in both the HelloRetryRequest and the final ServerHello per RFC 8446.
- If using a load balancer, ensure session affinity routes both HRR and the final handshake to the same backend.
- Report as a server-side TLS 1.3 implementation bug.
- As a workaround, restrict client to TLS 1.2 to avoid HRR entirely.
Defensive patterns
Strategy: try-catch
Try / catch
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
if strings.Contains(err.Error(), "changed cipher suite after a HelloRetryRequest") {
// Server bug — ensure session affinity or fall back to TLS 1.2
config.MaxVersion = tls.VersionTLS12
conn, err = tls.Dial("tcp", addr, config)
}
} Prevention
- Ensure load balancers maintain session affinity for TLS handshakes.
- Verify server TLS 1.3 HRR implementation with Wireshark captures.
- Report cipher suite consistency bugs to server vendors.
When it happens
Trigger: Triggered when hs.suite is already set (from a prior HelloRetryRequest) and mutualCipherSuiteTLS13() returns a different suite than hs.suite. The server changed its cipher suite selection mid-handshake.
Common situations: Server bug in HelloRetryRequest handling where the HRR and final ServerHello are processed by different code paths. Load balancer routing the HRR and final ServerHello to different backend servers. Server that recomputes cipher suite selection between HRR and the final handshake message.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: server sent an unnecessary HelloRetryRequest message
- tls: server selected an invalid PSK and cipher suite pair
- tls: server selected TLS 1.3 in a renegotiation
- tls: unexpected encrypted client hello extension in server h
- tls: server selected TLS 1.3 using the legacy version field
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/dc2d5073aa094542.
Report an issue: GitHub.