golang/go · error

tls: second client hello missing encrypted client hello exte

Error message

tls: second client hello missing encrypted client hello extension

What it means

When ECH (Encrypted Client Hello) was offered in the first ClientHello, the second ClientHello (after HelloRetryRequest) must also carry the ECH extension. If hs.echContext != nil but clientHello.encryptedClientHello is empty, the server sends missing_extension.

Source

Thrown at src/crypto/tls/handshake_server_tls13.go:596

		return nil, err
	}

	// clientHelloMsg is not included in the transcript.
	msg, err := c.readHandshake(nil)
	if err != nil {
		return nil, err
	}

	clientHello, ok := msg.(*clientHelloMsg)
	if !ok {
		c.sendAlert(alertUnexpectedMessage)
		return nil, unexpectedMessageError(clientHello, msg)
	}

	if hs.echContext != nil {
		if len(clientHello.encryptedClientHello) == 0 {
			c.sendAlert(alertMissingExtension)
			return nil, errors.New("tls: second client hello missing encrypted client hello extension")
		}

		echType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello)
		if err != nil {
			c.sendAlert(alertDecodeError)
			return nil, errors.New("tls: client sent invalid encrypted client hello extension")
		}

		if echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner {
			c.sendAlert(alertDecodeError)
			return nil, errors.New("tls: unexpected switch in encrypted client hello extension type")
		}

		if echType == outerECHExt {
			if echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 {
				c.sendAlert(alertIllegalParameter)
				return nil, errors.New("tls: second client hello encrypted client hello extension does not match")
			}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure ClientHello2 includes the ECH extension consistent with the HRR's ECH configuration
  2. Update the ECH-capable client library to a version that retries ECH correctly
Defensive patterns

Strategy: try-catch

Try / catch

if err := tlsConn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "second client hello missing encrypted client hello") {
        log.Printf("ECH dropped on retry from %v", remote)
    }
    c.Close()
    return
}

Prevention

When it happens

Trigger: Client offers ECH in ClientHello1, receives HRR, but omits the ECH extension in ClientHello2.

Common situations: Buggy ECH client that drops the extension on retry; partial ECH support; ECH draft-revision drift between client and server.

Understand the failure class

Related errors


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