golang/go · error
tls: unexpected encrypted client hello extension in server h
Error message
tls: unexpected encrypted client hello extension in server hello despite ECH being accepted
What it means
Thrown during TLS 1.3 ECH (Encrypted Client Hello) processing when the server's accept_confirmation value matches (ECH was accepted) but the ServerHello still contains an encrypted_client_hello extension. Per the ECH specification, once acceptance is confirmed, the server must not include the ECH extension in ServerHello.
Source
Thrown at src/crypto/tls/handshake_client_tls13.go:108
confTranscript.Write(hs.serverHello.original[:30])
confTranscript.Write(make([]byte, 8))
confTranscript.Write(hs.serverHello.original[38:])
h := hs.suite.hash.New
prk, err := hkdf.Extract(h, hs.echContext.innerHello.random, nil)
if err != nil {
c.sendAlert(alertInternalError)
return err
}
acceptConfirmation := tls13.ExpandLabel(h, prk, "ech accept confirmation", confTranscript.Sum(nil), 8)
if subtle.ConstantTimeCompare(acceptConfirmation, hs.serverHello.random[len(hs.serverHello.random)-8:]) == 1 {
hs.hello = hs.echContext.innerHello
c.serverName = c.config.ServerName
hs.transcript = hs.echContext.innerTranscript
c.echAccepted = true
if hs.serverHello.encryptedClientHello != nil {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: unexpected encrypted client hello extension in server hello despite ECH being accepted")
}
if hs.hello.serverName == "" && hs.serverHello.serverNameAck {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: unexpected server_name extension in server hello")
}
} else {
hs.echContext.echRejected = true
}
}
if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil {
return err
}
c.buffering = true
if err := hs.processServerHello(); err != nil {
return errView on GitHub (pinned to b6b368adc5)
Solutions
- Verify the ECH draft version on client and server match — ECH is still evolving through IETF drafts.
- If you control the server, ensure it omits encrypted_client_hello from ServerHello after ECH acceptance.
- If ECH is not required, remove config.EncryptedClientHelloConfigList from the client config to disable ECH entirely.
- Report the issue to the server's ECH implementation maintainer.
Defensive patterns
Strategy: try-catch
Try / catch
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
if strings.Contains(err.Error(), "ech being accepted") {
// Server bug in ECH handling — disable ECH and retry
config.EncryptedClientHelloConfigList = nil
conn, err = tls.Dial("tcp", addr, config)
}
} Prevention
- Verify ECH draft version compatibility between client and server.
- Disable ECH (remove EncryptedClientHelloConfigList) if the server's ECH implementation is buggy.
- Test ECH against known-good ECH servers (e.g. Cloudflare) before deploying.
When it happens
Trigger: Triggered inside checkServerHelloOrHRR() when hs.serverHello.encryptedClientHello is non-nil after ECH acceptance was confirmed via constant-time comparison of the accept_confirmation value. The client sends alertUnsupportedExtension.
Common situations: Server bug in ECH implementation that includes the extension after acceptance. Mismatched ECH draft versions between client and server causing divergent handling. Experimental or non-standard ECH server implementation.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: unexpected server_name extension in server hello
- tls: unexpected encrypted client hello extension in serverHe
- tls: server sent encrypted client hello retry configs after
- tls: server selected TLS 1.3 in a renegotiation
- 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/e006cfb225e9bf26.
Report an issue: GitHub.