golang/go · error

tls: server sent a cookie in a normal ServerHello

Error message

tls: server sent a cookie in a normal ServerHello

What it means

In a genuine ServerHello (not an HRR), RFC 8446 §4.2.2 forbids the cookie extension; cookies belong only in HelloRetryRequest. Go detects a non-empty hs.serverHello.cookie and sends an `unsupported_extension` alert. Indicates a server that is misusing the cookie extension.

Source

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

	if err := hs.checkServerHelloOrHRR(); err != nil {
		return err
	}

	c.didHRR = true
	return nil
}

func (hs *clientHandshakeStateTLS13) processServerHello() error {
	c := hs.c

	if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
		c.sendAlert(alertUnexpectedMessage)
		return errors.New("tls: server sent two HelloRetryRequest messages")
	}

	if len(hs.serverHello.cookie) != 0 {
		c.sendAlert(alertUnsupportedExtension)
		return errors.New("tls: server sent a cookie in a normal ServerHello")
	}

	if hs.serverHello.selectedGroup != 0 {
		c.sendAlert(alertDecodeError)
		return errors.New("tls: malformed key_share extension")
	}

	if hs.serverHello.serverShare.group == 0 {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server did not send a key share")
	}
	if !slices.ContainsFunc(hs.hello.keyShares, func(ks keyShare) bool {
		return ks.group == hs.serverHello.serverShare.group
	}) {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server selected unsupported group")
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Report to the server operator — the cookie extension is HRR-only.
  2. Capture the handshake with Wireshark to confirm the offending extension is coming from the server, not a proxy.
  3. Eliminate any TLS-terminating proxy/load-balancer in the path that may be injecting extensions.
  4. If self-operated, fix the server's ServerHello encoder to omit cookie outside HRR.
Defensive patterns

Strategy: try-catch

Try / catch

conn, err := tls.Dial("tcp", addr, cfg)
if err != nil && strings.Contains(err.Error(), "cookie in a normal ServerHello") {
    // cookie is HRR-only; the server or a proxy is misbehaving
    log.Printf("peer %s injected a cookie outside HRR", addr)
    return err
}

Prevention

When it happens

Trigger: ServerHello (random != helloRetryRequestRandom) carries a non-empty cookie field. Reached in processServerHello during a normal TLS 1.3 handshake.

Common situations: Server implementation that always echoes the cookie, fuzz-generated ServerHello, or a malicious peer probing the client. Normal OpenSSL/BoringSSL/Go servers never do this.

Understand the failure class

Related errors


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