golang/go · error
tls: client used the legacy version field to negotiate TLS 1
Error message
tls: client used the legacy version field to negotiate TLS 1.3
What it means
RFC 8446 §4.2.1 requires a TLS 1.3 client to advertise its supported versions via the supported_versions extension, NOT the legacy ClientHello.version field (which must stay 0x0303). This error fires in processClientHello when hs.clientHello.supportedVersions is empty — the client tried to negotiate TLS 1.3 through the legacy field. The server sends an illegal_parameter alert and aborts.
Source
Thrown at src/crypto/tls/handshake_server_tls13.go:119
c.isHandshakeComplete.Store(true)
return nil
}
func (hs *serverHandshakeStateTLS13) processClientHello() error {
c := hs.c
hs.hello = new(serverHelloMsg)
// TLS 1.3 froze the ServerHello.legacy_version field, and uses
// supported_versions instead. See RFC 8446, sections 4.1.3 and 4.2.1.
hs.hello.vers = VersionTLS12
hs.hello.supportedVersion = c.vers
if len(hs.clientHello.supportedVersions) == 0 {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: client used the legacy version field to negotiate TLS 1.3")
}
// Abort if the client is doing a fallback and landing lower than what we
// support. See RFC 7507, which however does not specify the interaction
// with supported_versions. The only difference is that with
// supported_versions a client has a chance to attempt a [TLS 1.2, TLS 1.4]
// handshake in case TLS 1.3 is broken but 1.2 is not. Alas, in that case,
// it will have to drop the TLS_FALLBACK_SCSV protection if it falls back to
// TLS 1.2, because a TLS 1.3 server would abort here. The situation before
// supported_versions was not better because there was just no way to do a
// TLS 1.4 handshake without risking the server selecting TLS 1.3.
for _, id := range hs.clientHello.cipherSuites {
if id == TLS_FALLBACK_SCSV {
// Use c.vers instead of max(supported_versions) because an attacker
// could defeat this by adding an arbitrary high version otherwise.
if c.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) {
c.sendAlert(alertInappropriateFallback)
return errors.New("tls: client using inappropriate protocol fallback")View on GitHub (pinned to b6b368adc5)
Solutions
- Ensure the client sends the supported_versions extension listing 0x0304 (TLS 1.3)
- Keep ClientHello.legacy_version pinned at 0x0303 (TLS 1.2) as mandated by RFC 8446 §4.1.2
- Replace a hand-rolled TLS client with an RFC 8446-compliant library (Go's crypto/tls, current OpenSSL/BoringSSL)
Example fix
// before (non-compliant client)
hello.version = 0x0304 // TLS 1.3 via legacy field
// supported_versions omitted
// after (compliant)
hello.version = 0x0303
hello.supportedVersions = []uint16{0x0304, 0x0303} Defensive patterns
Strategy: try-catch
Validate before calling
// Server-side: you cannot pre-validate a remote ClientHello.
// Ensure YOUR server config does not force TLS 1.3-only in a way that
// rejects compliant clients. Default config is fine.
cfg := &tls.Config{ /* leave MinVersion/MaxVersion at defaults */ } Try / catch
ln, err := listener.Accept()
if err != nil { log.Printf("accept: %v", err); continue }
go func(c net.Conn) {
tlsConn := tls.Server(c, cfg)
if err := tlsConn.Handshake(); err != nil {
if strings.Contains(err.Error(), "legacy version field") {
log.Printf("non-compliant client (no supported_versions): %v", err)
}
c.Close()
return
}
handle(tlsConn)
}(ln) Prevention
- Log handshake errors with the remote address to spot non-compliant or scanning clients
- Do not hand-roll TLS clients; use crypto/tls or a vetted library that always sends supported_versions
- If you operate a proxy, ensure it does not strip the supported_versions extension
When it happens
Trigger: A ClientHello arrives with an empty supported_versions list. This happens when a hand-rolled or non-compliant TLS client sets legacy_version directly to 0x0304 and omits the supported_versions extension, or when an old TLS stack predating RFC 8446 is used.
Common situations: Custom/in-house TLS client implementations, security fuzzers generating malformed ClientHellos, old TLS libraries that predate RFC 8446, or middleboxes/proxies that rewrite and strip the ClientHello.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: TLS 1.3 client supports illegal compression methods
- tls: initial handshake had non-empty renegotiation extension
- tls: invalid or missing PSK binders
- tls: handshake buffer not empty before HelloRetryRequest
- tls: missing signature_algorithms from TLS 1.2 peer
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/3406d0de3260b6be.
Report an issue: GitHub.