golang/go · error

tls: server selected TLS 1.3 in a renegotiation

Error message

tls: server selected TLS 1.3 in a renegotiation

What it means

Thrown in clientHandshakeStateTLS13.handshake() when the server selects TLS 1.3 during a renegotiation (c.handshakes > 0). RFC 8446 sections 4.1.2 and 4.1.3 explicitly prohibit renegotiation in TLS 1.3 — TLS 1.3 replaces it with post-handshake authentication and KeyUpdate.

Source

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

	sentDummyCCS  bool
	suite         *cipherSuiteTLS13
	transcript    hash.Hash
	masterSecret  *tls13.MasterSecret
	trafficSecret []byte // client_application_traffic_secret_0

	echContext *echClientContext
}

// handshake requires hs.c, hs.hello, hs.serverHello, hs.keyShareKeys, and,
// optionally, hs.session, hs.earlySecret and hs.binderKey to be set.
func (hs *clientHandshakeStateTLS13) handshake() error {
	c := hs.c

	// The server must not select TLS 1.3 in a renegotiation. See RFC 8446,
	// sections 4.1.2 and 4.1.3.
	if c.handshakes > 0 {
		c.sendAlert(alertProtocolVersion)
		return errors.New("tls: server selected TLS 1.3 in a renegotiation")
	}

	// Consistency check on the presence of a keyShare and its parameters.
	if hs.keyShareKeys == nil || (hs.keyShareKeys.ecdhe == nil && hs.keyShareKeys.mlkem == nil) ||
		len(hs.hello.keyShares) == 0 {
		return c.sendAlert(alertInternalError)
	}

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

	hs.transcript = hs.suite.hash.New()

	if err := transcriptMsg(hs.hello, hs.transcript); err != nil {
		return err
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Do not renegotiate TLS 1.3 connections — use post-handshake authentication (tls.Conn.VerifyPeerCertificate callback) or KeyUpdate instead.
  2. Replace renegotiation-based connection reuse with a connection pool that creates fresh TLS connections.
  3. Pin MaxVersion to tls.VersionTLS12 if renegotiation is a hard requirement and cannot be refactored away.
  4. Refactor the re-authentication pattern: close and re-dial instead of renegotiating.

Example fix

// before — renegotiation fails on TLS 1.3
conn := // existing TLS connection
err := conn.Renegotiate(tls.RenegotiateOnceAsClient)

// after — establish a fresh connection instead
conn.Close()
conn, err = tls.Dial("tcp", addr, config)
Defensive patterns

Strategy: try-catch

Try / catch

// Renegotiation errors are untyped strings
err := conn.Renegotiate(tls.RenegotiateOnceAsClient)
if err != nil {
    if strings.Contains(err.Error(), "server selected TLS 1.3 in a renegotiation") {
        // TLS 1.3 does not support renegotiation — dial a fresh connection
        conn.Close()
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered when c.handshakes > 0 and the server's selected version is TLS 1.3. The client sends alertProtocolVersion and aborts.

Common situations: Calling Conn.Renegotiate() on a connection that the server then negotiates at TLS 1.3. Custom connection pooling that attempts renegotiation for re-authentication. Server-initiated renegotiation on an already-established TLS 1.3 connection. Code written for TLS 1.2 renegotiation that breaks under TLS 1.3.

Understand the failure class

Related errors


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