golang/go · error

tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHe

Error message

tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated

What it means

Thrown by makeClientHello when config.EncryptedClientHelloConfigList is non-nil (ECH is enabled on the client) AND config.MaxVersion is explicitly set to VersionTLS12 (0x0303) or below. ECH requires TLS 1.3+, so capping the maximum version at 1.2 makes ECH impossible. This check fires when MaxVersion != 0 and MaxVersion <= VersionTLS12.

Source

Thrown at src/crypto/tls/handshake_client.go:180

	if c.quic != nil {
		p, err := c.quicGetTransportParameters()
		if err != nil {
			return nil, nil, nil, err
		}
		if p == nil {
			p = []byte{}
		}
		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

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Set MaxVersion to tls.VersionTLS13 (or leave it as 0 for the default maximum, which includes TLS 1.3)
  2. Remove the MaxVersion cap entirely if ECH is required — ECH cannot work with a TLS 1.2 ceiling
  3. If you genuinely need TLS 1.2 as the maximum version, do not enable ECH — they are incompatible

Example fix

// before
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MaxVersion:                    tls.VersionTLS12,
}
// after
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MaxVersion:                    tls.VersionTLS13,
}
Defensive patterns

Strategy: validation

Validate before calling

func validateECHClientConfig(config *tls.Config) error {
    if config.EncryptedClientHelloConfigList == nil {
        return nil
    }
    if config.MinVersion != 0 && config.MinVersion < tls.VersionTLS13 {
        return errors.New("MinVersion must be >= VersionTLS13 when ECH is enabled")
    }
    if config.MaxVersion != 0 && config.MaxVersion <= tls.VersionTLS12 {
        return errors.New("MaxVersion must be >= VersionTLS13 when ECH is enabled")
    }
    return nil
}

Try / catch

// Pre-validate before dial:
//
//   if err := validateECHClientConfig(config); err != nil {
//       config.MaxVersion = 0 // reset to default (allows TLS 1.3)
//   }

Prevention

When it happens

Trigger: Setting both EncryptedClientHelloConfigList (to a non-nil byte slice) and MaxVersion to VersionTLS12 (0x0303), VersionTLS11 (0x0302), or VersionTLS10 (0x0301) simultaneously.

Common situations: Configuring ECH while keeping MaxVersion=VersionTLS12 for legacy server compatibility. Adding ECH to a config designed for TLS 1.2-only environments. Copying a config with a TLS 1.2 ceiling and enabling ECH without raising MaxVersion.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/1060f321cbd02dbd. Report an issue: GitHub.