golang/go · error

tls: unexpected server_name extension in server hello

Error message

tls: unexpected server_name extension in server hello

What it means

Thrown during TLS 1.3 ECH processing when ECH was accepted but the inner ClientHello had no server_name (SNI), yet the server's ServerHello includes a server_name acknowledgment. This is contradictory: if the inner hello didn't advertise a name, the server must not acknowledge one.

Source

Thrown at src/crypto/tls/handshake_client_tls13.go:113

		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 err
	}
	if err := hs.sendDummyChangeCipherSpec(); err != nil {
		return err
	}
	if err := hs.establishHandshakeKeys(); err != nil {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Set config.ServerName to the real backend hostname when using ECH — the inner hello needs a valid SNI.
  2. Verify EncryptedClientHelloConfigList entries have correct DNS names matching config.ServerName.
  3. If ECH is not needed, remove config.EncryptedClientHelloConfigList to disable ECH.
  4. Ensure the ECH inner SNI and outer SNI are configured consistently.

Example fix

// before — ECH without inner SNI
config := &tls.Config{
    EncryptedClientHelloConfigList: echList,
    // ServerName missing!
}

// after — set the real hostname for the inner hello
config := &tls.Config{
    ServerName:                      "backend.example.com",
    EncryptedClientHelloConfigList: echList,
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate ECH config has a ServerName for the inner hello
func validateECHConfig(config *tls.Config) error {
    if config.EncryptedClientHelloConfigList != nil && config.ServerName == "" {
        return fmt.Errorf("ServerName must be set when using ECH — the inner hello needs an SNI")
    }
    return nil
}

Try / catch

conn, err := tls.Dial("tcp", addr, config)
if err != nil {
    if strings.Contains(err.Error(), "unexpected server_name extension in server hello") {
        // Set ServerName for the inner hello and retry
        config.ServerName = hostname
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered when hs.hello.serverName is empty (the inner hello has no SNI) AND hs.serverHello.serverNameAck is true after ECH acceptance is confirmed. The client sends alertUnsupportedExtension.

Common situations: ECH inner hello configured without a ServerName because config.ServerName was left empty. Mismatch between the inner ClientHello's SNI and the server's response. Server bug in ECH server_name acknowledgment logic.

Understand the failure class

Related errors


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