{"record":{"id":"dc2d5073aa094542","repo":"golang/go","slug":"tls-server-changed-cipher-suite-after-a-helloretr","errorCode":null,"errorMessage":"tls: server changed cipher suite after a HelloRetryRequest","messagePattern":"tls: server changed cipher suite after a HelloRetryRequest","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/tls/handshake_client_tls13.go","lineNumber":207,"sourceCode":"\t\tlen(hs.serverHello.scts) != 0 {\n\t\tc.sendAlert(alertUnsupportedExtension)\n\t\treturn errors.New(\"tls: server sent a ServerHello extension forbidden in TLS 1.3\")\n\t}\n\n\tif !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: server did not echo the legacy session ID\")\n\t}\n\n\tif hs.serverHello.compressionMethod != compressionNone {\n\t\tc.sendAlert(alertDecodeError)\n\t\treturn errors.New(\"tls: server sent non-zero legacy TLS compression method\")\n\t}\n\n\tselectedSuite := mutualCipherSuiteTLS13(hs.hello.cipherSuites, hs.serverHello.cipherSuite)\n\tif hs.suite != nil && selectedSuite != hs.suite {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: server changed cipher suite after a HelloRetryRequest\")\n\t}\n\tif selectedSuite == nil {\n\t\tc.sendAlert(alertIllegalParameter)\n\t\treturn errors.New(\"tls: server chose an unconfigured cipher suite\")\n\t}\n\ths.suite = selectedSuite\n\tc.cipherSuite = hs.suite.id\n\n\treturn nil\n}\n\n// sendDummyChangeCipherSpec sends a ChangeCipherSpec record for compatibility\n// with middleboxes that didn't implement TLS correctly. See RFC 8446, Appendix D.4.\nfunc (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {\n\tif hs.c.quic != nil {\n\t\treturn nil\n\t}\n\tif hs.sentDummyCCS {","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/tls/handshake_client_tls13.go#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"conn, err := tls.Dial(\"tcp\", addr, config)\nif err != nil {\n    if strings.Contains(err.Error(), \"changed cipher suite after a HelloRetryRequest\") {\n        // Server bug — ensure session affinity or fall back to TLS 1.2\n        config.MaxVersion = tls.VersionTLS12\n        conn, err = tls.Dial(\"tcp\", addr, config)\n    }\n}","preventionTips":["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."],"tags":["tls","go","tls13","cipher-suite","hello-retry-request","protocol-violation"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}