golang/go · error

tls: client using inappropriate protocol fallback

Error message

tls: client using inappropriate protocol fallback

What it means

The client included TLS_FALLBACK_SCSV in its cipher suites but offered a version lower than the server's maximum supported version. Per RFC 7507, this sentinel signals intentional protocol downgrade protection; a legitimate downgrade should not occur, so the server rejects with inappropriate_fallback.

Source

Thrown at src/crypto/tls/handshake_server.go:416

func (hs *serverHandshakeState) pickCipherSuite() error {
	c := hs.c

	preferenceList := c.config.cipherSuites(isAESGCMPreferred(hs.clientHello.cipherSuites))

	hs.suite = selectCipherSuite(preferenceList, hs.clientHello.cipherSuites, hs.cipherSuiteOk)
	if hs.suite == nil {
		c.sendAlert(alertHandshakeFailure)
		return fmt.Errorf("tls: no cipher suite supported by both client and server; client offered: %x",
			hs.clientHello.cipherSuites)
	}
	c.cipherSuite = hs.suite.id

	for _, id := range hs.clientHello.cipherSuites {
		if id == TLS_FALLBACK_SCSV {
			// The client is doing a fallback connection. See RFC 7507.
			if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) {
				c.sendAlert(alertInappropriateFallback)
				return errors.New("tls: client using inappropriate protocol fallback")
			}
			break
		}
	}

	return nil
}

func (hs *serverHandshakeState) cipherSuiteOk(c *cipherSuite) bool {
	if c.flags&suiteECDHE != 0 {
		if !hs.ecdheOk {
			return false
		}
		if c.flags&suiteECSign != 0 {
			if !hs.ecSignOk {
				return false
			}
		} else if !hs.rsaSignOk {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Stop forcing the downgrade — connect at the highest mutually supported version (TLS 1.3).
  2. If a downgrade retry is genuinely needed, ensure the server also lacks support for the higher version; otherwise do not send FALLBACK_SCSV.
  3. Remove middleware or proxies that cap MaxVersion below what the server supports.
  4. Update the client's retry logic to not inject FALLBACK_SCSV on the initial connection attempt.

Example fix

// before: client forces TLS 1.2 and signals fallback
cfg := &tls.Config{
    MaxVersion: tls.VersionTLS12,
    // somewhere FALLBACK_SCSV is appended
}

// after: let the client negotiate the highest version
cfg := &tls.Config{
    MinVersion: tls.VersionTLS12,
    // MaxVersion unset — defaults to highest supported
Defensive patterns

Strategy: validation

Validate before calling

// Client: do not inject TLS_FALLBACK_SCSV unless performing an intentional
// downgrade retry, and only after confirming the server does not support a
// higher version.
// Standard libraries handle this correctly — avoid manual SCSV injection.

Try / catch

// Client: catch and stop the downgrade loop.
if err != nil && strings.Contains(err.Error(), "inappropriate protocol fallback") {
    // do NOT retry at an even lower version; report the failure
    return err
}

Prevention

When it happens

Trigger: pickCipherSuite iterates hs.clientHello.cipherSuites; finding TLS_FALLBACK_SCSV and hs.clientHello.vers < server's max supported version triggers alertInappropriateFallback.

Common situations: A client library that automatically retries with a lower TLS version after a failure and incorrectly sends FALLBACK_SCSV, a misconfigured proxy forcing downgrade, or a MITM attempting version rollback. Browser-style fallback mechanisms must only send SCSV when the downgrade is intentional AND no higher version works against this specific server.

Understand the failure class

Related errors


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