golang/go · error
tls: EncryptedClientHelloConfigList contains no valid config
Error message
tls: EncryptedClientHelloConfigList contains no valid configs
What it means
Thrown after parsing Config.EncryptedClientHelloConfigList when pickECHConfig(echConfigs) returns a nil config. The list parsed successfully (parseECHConfigList did not error) but no entry had a usable combination of HPKE KEM/KDF/AEAD that this Go build supports. ECH (Encrypted Client Hello, draft-ietf-tls-esni) wraps the real ClientHello inside an HPKE-sealed inner hello to hide the SNI.
Source
Thrown at src/crypto/tls/handshake_client.go:188
}
hello.quicTransportParameters = p
}
var ech *echClientContext
if c.config.EncryptedClientHelloConfigList != nil {
if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}
if c.config.MaxVersion != 0 && c.config.MaxVersion <= VersionTLS12 {
return nil, nil, nil, errors.New("tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}
echConfigs, err := parseECHConfigList(c.config.EncryptedClientHelloConfigList)
if err != nil {
return nil, nil, nil, err
}
echConfig, echPK, kdf, aead := pickECHConfig(echConfigs)
if echConfig == nil {
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
}
ech = &echClientContext{config: echConfig, kdfID: kdf.ID(), aeadID: aead.ID()}
hello.encryptedClientHello = []byte{1} // indicate inner hello
// We need to explicitly set these 1.2 fields to nil, as we do not
// marshal them when encoding the inner hello, otherwise transcripts
// will later mismatch.
hello.supportedPoints = nil
hello.ticketSupported = false
hello.secureRenegotiationSupported = false
hello.extendedMasterSecret = false
info := append([]byte("tls ech\x00"), ech.config.raw...)
ech.encapsulatedKey, ech.hpkeContext, err = hpke.NewSender(echPK, kdf, aead, info)
if err != nil {
return nil, nil, nil, err
}
}
View on GitHub (pinned to b6b368adc5)
Solutions
- Refetch the ECH config at runtime from the authoritative DNS HTTPS/SVCB ech= parameter (base64-decoded) rather than caching it long-term.
- Verify you are passing only the ech= value (base64-decoded bytes), not the entire HTTPS RR or the quoted string.
- Upgrade Go to a version whose crypto/internal/hpke supports the KEM/KDF/AEAD in the config (X25519 + HKDF-SHA256 + AES-128-GCM is the baseline).
- If ECH cannot be made reliable, set Config.EncryptedClientHelloConfigList to nil and fall back to plaintext SNI rather than failing every handshake.
Example fix
// before: stale hardcoded list
cfg := &tls.Config{EncryptedClientHelloConfigList: cachedECHBytes}
// after: fetch fresh at connection time and tolerate absence
echBytes, err := dnshttps.ECHConfig(serverName) // resolves type 65
cfg := &tls.Config{}
if err == nil && len(echBytes) > 0 {
cfg.EncryptedClientHelloConfigList = echBytes
} Defensive patterns
Strategy: validation
Validate before calling
// Parse the ECH config list the same way the library will, and check that
// at least one config is acceptable before dialing.
import "crypto/internal/fips140/hpke" // not publicly exposed; mirror the supported suites
func hasUsableECHConfig(list []byte) bool {
// Walk 4-byte version + 1-byte length-prefixed ECHConfig entries (draft-ietf-tls-esni).
for len(list) > 0 {
if len(list) < 4 { return false }
// version is uint16 at list[0:2]; config_id length follows
n := int(list[3])
if 4+n > len(list) { return false }
cfg := list[4 : 4+n]
if echSupported(cfg) { return true }
list = list[4+n:]
}
return false
}
// echSupported should check the HPKE KEM/KDF/AEAD triple is one your Go supports.
// In practice: fetch a fresh config list at runtime and rely on Go's own parsing. Type guard
// No type guard: errors are untyped strings. Match on the message prefix.
func isECHNoValidConfigs(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "tls: EncryptedClientHelloConfigList contains no valid configs")
} Try / catch
echBytes, err := resolveECHConfig(host)
if err == nil { cfg.EncryptedClientHelloConfigList = echBytes }
if _, derr := tls.Dial("tcp", addr, cfg); derr != nil {
if isECHNoValidConfigs(derr) {
// retry once with ECH disabled rather than failing permanently
cfg.EncryptedClientHelloConfigList = nil
_, derr = tls.Dial("tcp", addr, cfg)
}
} Prevention
- Fetch ECH configs at runtime from DNS HTTPS records; never hardcode them.
- Pass only the base64-decoded ech= value, not the whole HTTPS RR.
- Upgrade Go alongside ECH draft revisions.
When it happens
Trigger: Calling tls.Dial / tls.DialTLS / http transport with Config.EncryptedClientHelloConfigList set to bytes where every entry uses an unsupported KEM (e.g. a post-quantum KEM this Go version lacks), uses an ECHConfigVersion other than 0xfffd, or has a public_name/key_config that pickECHConfig rejects. Common when the caller passes the whole DNS HTTPS RR instead of just the ech= base64 payload.
Common situations: Stale ECH config cached locally after the server rotated its HPKE key; fetching the ECH config from DNS HTTPS/SVCB records on a Go version older than the suite the server advertises; passing raw bytes that contain non-ECH SvcParamKeys; empty list after filtering malformed entries.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: MinVersion must be >= VersionTLS13 if EncryptedClientHe
- tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHe
- tls: failed to sign handshake: {err}
- ECDSA verification failure
- Ed25519 verification failure
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/2fab0f092da443bf.
Report an issue: GitHub.