golang/go · error
tls: server sent encrypted client hello retry configs after
Error message
tls: server sent encrypted client hello retry configs after accepting encrypted client hello
What it means
Encrypted Client Hello (ECH) specific: the server accepted ECH (hs.echContext.echRejected is false) but still included ECH retry configs in EncryptedExtensions. RFC 9460 only permits retry configs when ECH was rejected. Go sends `unsupported_extension`. Indicates a server misusing the ECH retry_config field.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:583
if hs.hello.earlyData && !encryptedExtensions.earlyData {
c.quicRejectedEarlyData()
}
if encryptedExtensions.earlyData {
if hs.session.cipherSuite != c.cipherSuite {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server accepted 0-RTT with the wrong cipher suite")
}
if hs.session.alpnProtocol != c.clientProtocol {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server accepted 0-RTT with the wrong ALPN")
}
}
if hs.echContext != nil {
if hs.echContext.echRejected {
hs.echContext.retryConfigs = encryptedExtensions.echRetryConfigs
} else if encryptedExtensions.echRetryConfigs != nil {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: server sent encrypted client hello retry configs after accepting encrypted client hello")
}
}
return nil
}
func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
c := hs.c
// Either a PSK or a certificate is always used, but not both.
// See RFC 8446, Section 4.1.1.
if hs.usingPSK {
// Make sure the connection is still being verified whether or not this
// is a resumption. Resumptions currently don't reverify certificates so
// they don't call verifyServerCertificate. See Issue 31641.
if c.config.VerifyConnection != nil {
if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
c.sendAlert(alertBadCertificate)View on GitHub (pinned to b6b368adc5)
Solutions
- Report to the server operator — retry_configs are rejection-only per RFC 9460.
- Confirm ECH is actually being accepted (check the ECH status after the handshake) on a known-good peer.
- Update the client and server ECH implementations to draft-compliant versions (ECH is still evolving).
- Disable ECH (do not set Config.EncryptedClientHelloConfigList) if the server implementation is unreliable.
Example fix
// before: ECH configured against a server with buggy retry-config handling
cfg := &tls.Config{EncryptedClientHelloConfigList: echList}
// after: disable ECH until the server is fixed
cfg := &tls.Config{} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: ECH is still maturing; only enable it against peers known to implement RFC 9460 correctly.
if !echCapablePeer {
cfg.EncryptedClientHelloConfigList = nil
} Try / catch
if err := conn.Handshake(); err != nil {
if strings.Contains(err.Error(), "encrypted client hello retry configs after accepting") {
cfg.EncryptedClientHelloConfigList = nil // disable ECH for this peer
return retryHandshake(addr, cfg)
}
} Prevention
- Track ECH draft/RFC compliance of peers before enabling ECH.
- Update both client and server ECH implementations in lockstep.
- Log ECH outcomes to detect servers with buggy retry-config logic.
When it happens
Trigger: Client offered ECH, server accepted it (inner SNI used), yet encryptedExtensions.echRetryConfigs is non-nil. Reached in processEncryptedExtensions when ECH is in use.
Common situations: Experimental/early ECH server implementations, fuzzed handshakes, or a server that always includes retry_configs. Mainstream ECH-capable servers gate retry_configs on rejection.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: server did not send a quic_transport_parameters extensi
- tls: server sent an unexpected quic_transport_parameters ext
- tls: unexpected encrypted client hello extension in server h
- tls: unexpected encrypted client hello extension in serverHe
- tls: server sent two HelloRetryRequest messages
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/54802d962d42cb48.
Report an issue: GitHub.