golang/go · error

tls: server sent an unexpected quic_transport_parameters ext

Error message

tls: server sent an unexpected quic_transport_parameters extension

What it means

Mirror of 630: when the connection is NOT in QUIC mode (c.quic == nil), EncryptedExtensions must NOT carry quic_transport_parameters. Go sends `unsupported_extension` if it does. Indicates a regular TLS 1.3 server incorrectly emitting a QUIC-only extension.

Source

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

		// does not specify how clients handle the selection of an incompatible protocol.
		// RFC 9001 Section 8.1 specifies that QUIC clients send no_application_protocol
		// in this case. Always sending no_application_protocol seems reasonable.
		c.sendAlert(alertNoApplicationProtocol)
		return err
	}
	c.clientProtocol = encryptedExtensions.alpnProtocol

	if c.quic != nil {
		if encryptedExtensions.quicTransportParameters == nil {
			// RFC 9001 Section 8.2.
			c.sendAlert(alertMissingExtension)
			return errors.New("tls: server did not send a quic_transport_parameters extension")
		}
		c.quicSetTransportParameters(encryptedExtensions.quicTransportParameters)
	} else {
		if encryptedExtensions.quicTransportParameters != nil {
			c.sendAlert(alertUnsupportedExtension)
			return errors.New("tls: server sent an unexpected quic_transport_parameters extension")
		}
	}

	if !hs.hello.earlyData && encryptedExtensions.earlyData {
		c.sendAlert(alertUnsupportedExtension)
		return errors.New("tls: server sent an unexpected early_data extension")
	}
	if hs.hello.earlyData && !encryptedExtensions.earlyData {
		c.quicRejectedEarlyData()
	}
	if encryptedExtensions.earlyData {
		if hs.session.cipherSuite != c.cipherSuite {
			c.sendAlert(alertHandshakeFailure)
			return errors.New("tls: server accepted 0-RTT with the wrong cipher suite")
		}
		if hs.session.alpnProtocol != c.clientProtocol {
			c.sendAlert(alertHandshakeFailure)
			return errors.New("tls: server accepted 0-RTT with the wrong ALPN")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Report to the server operator — the extension is QUIC-only.
  2. Capture the EncryptedExtensions to confirm the unexpected extension is server-origin.
  3. Test the same hostname over a known client (openssl s_client) to cross-check.
  4. Patch the server to only emit the extension on QUIC transports.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: ensure you are not accidentally enabling QUIC mode for a TCP connection.
// QUIC mode is set by the QUIC library via non-exported hooks; using tls.Dial keeps c.quic == nil.
// If you see this error, the server is at fault — no client-side prevention.

Try / catch

if err := conn.Handshake(); err != nil {
    if strings.Contains(err.Error(), "unexpected quic_transport_parameters") {
        log.Printf("server %s emitted a QUIC-only extension over TCP", addr)
    }
    return err
}

Prevention

When it happens

Trigger: Standard tls.Dial/tls.Client handshake where the server's EncryptedExtensions includes quic_transport_parameters. c.quic is nil because the caller did not enable QUIC mode.

Common situations: Server that always sends quic_transport_parameters regardless of transport, a misconfigured proxy, or a TLS library bug on the server. Should not occur against compliant servers.

Understand the failure class

Related errors


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