golang/go · error

tls: server selected TLS 1.3 using the legacy version field

Error message

tls: server selected TLS 1.3 using the legacy version field

What it means

Thrown in checkServerHelloOrHRR() when the ServerHello's supported_versions extension value is zero (absent). TLS 1.3 requires version negotiation via the supported_versions extension (RFC 8446 section 4.2.1) — using only the legacy record version field is not permitted.

Source

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

	if hs.echContext != nil && hs.echContext.echRejected {
		c.sendAlert(alertECHRequired)
		return &ECHRejectionError{hs.echContext.retryConfigs}
	}

	c.isHandshakeComplete.Store(true)

	return nil
}

// checkServerHelloOrHRR does validity checks that apply to both ServerHello and
// HelloRetryRequest messages. It sets hs.suite.
func (hs *clientHandshakeStateTLS13) checkServerHelloOrHRR() error {
	c := hs.c

	if hs.serverHello.supportedVersion == 0 {
		c.sendAlert(alertMissingExtension)
		return errors.New("tls: server selected TLS 1.3 using the legacy version field")
	}

	if hs.serverHello.supportedVersion != VersionTLS13 {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server selected an invalid version after a HelloRetryRequest")
	}

	if hs.serverHello.vers != VersionTLS12 {
		c.sendAlert(alertIllegalParameter)
		return errors.New("tls: server sent an incorrect legacy version")
	}

	if hs.serverHello.ocspStapling ||
		hs.serverHello.ticketSupported ||
		hs.serverHello.extendedMasterSecret ||
		hs.serverHello.secureRenegotiationSupported ||
		len(hs.serverHello.secureRenegotiation) != 0 ||
		len(hs.serverHello.alpnProtocol) != 0 ||

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Server must include a supported_versions extension with 0x0304 (TLS 1.3) in the ServerHello.
  2. If the server cannot be fixed, restrict the client to TLS 1.2: set MaxVersion: tls.VersionTLS12.
  3. Check for middlebox interference — test with a direct connection.
  4. Report as a server compliance bug to the server software vendor.

Example fix

// before — client offers TLS 1.3, server doesn't implement supported_versions
config := &tls.Config{
    MinVersion: tls.VersionTLS13,
}

// after — fall back to TLS 1.2 if server is non-compliant
config := &tls.Config{
    MinVersion: tls.VersionTLS12,
    MaxVersion: tls.VersionTLS12,
}
Defensive patterns

Strategy: try-catch

Try / catch

// Version negotiation errors are untyped strings
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
    if strings.Contains(err.Error(), "using the legacy version field") {
        // Server doesn't implement supported_versions — fall back to TLS 1.2
        config.MaxVersion = tls.VersionTLS12
        conn, err = tls.Dial("tcp", addr, config)
    }
}

Prevention

When it happens

Trigger: Triggered when hs.serverHello.supportedVersion == 0. This means the server attempted to negotiate TLS 1.3 using only the legacy version field (0x0303) without including the mandatory supported_versions extension in the ServerHello.

Common situations: Non-compliant server that does not implement the supported_versions extension. TLS middlebox or proxy stripping extensions from the ServerHello. Server implementation that predates TLS 1.3 and incorrectly sets the version field. Broken TLS terminator in a load balancer.

Understand the failure class

Related errors


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