golang/go · error

tls: MinVersion must be >= VersionTLS13 if EncryptedClientHe

Error message

tls: MinVersion 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.MinVersion is explicitly set to a value below VersionTLS13 (0x0304). ECH is a TLS 1.3-only extension per RFC 9460, so requiring a minimum version below 1.3 while enabling ECH is contradictory and rejected at configuration time.

Source

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

			hello.keyShares = hello.keyShares[:1]
		}
	}

	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

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Set MinVersion to tls.VersionTLS13 (or higher) when using ECH
  2. Leave MinVersion as 0 (zero value) when using ECH — the zero value allows TLS 1.3 and the ECH code path works correctly
  3. If you need TLS 1.2 fallback, do not use ECH — ECH and TLS 1.2 are mutually exclusive

Example fix

// before
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MinVersion:                    tls.VersionTLS12,
}
// after
config := &tls.Config{
    EncryptedClientHelloConfigList: echConfigList,
    MinVersion:                    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 {
//       log.Fatal(err)
//   }
//   conn, err := tls.Dial("tcp", addr, config)

Prevention

When it happens

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

Common situations: Adding ECH to an existing tls.Config that had MinVersion=VersionTLS12 for legacy compatibility. Copying a production config that restricted MinVersion and adding ECH without updating the version floor. Misunderstanding that ECH requires TLS 1.3.

Understand the failure class

Related errors


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