golang/go · error

tls: server did not select an ALPN protocol

Error message

tls: server did not select an ALPN protocol

What it means

In checkALPN, when quic == true (the connection is over QUIC) and the client advertised at least one ALPN protocol (len(clientProtos) > 0) but the server returned an empty selection (serverProto == ""), RFC 9001 8.1 makes this a connection error. For TCP TLS an empty selection is allowed; QUIC requires the server to pick one.

Source

Thrown at src/crypto/tls/handshake_client.go:974

	c.verifiedChains = hs.session.verifiedChains
	c.ocspResponse = hs.session.ocspResponse
	// Let the ServerHello SCTs override the session SCTs from the original
	// connection, if any are provided.
	if len(c.scts) == 0 && len(hs.session.scts) != 0 {
		c.scts = hs.session.scts
	}
	c.curveID = hs.session.curveID

	return true, nil
}

// checkALPN ensure that the server's choice of ALPN protocol is compatible with
// the protocols that we advertised in the ClientHello.
func checkALPN(clientProtos []string, serverProto string, quic bool) error {
	if serverProto == "" {
		if quic && len(clientProtos) > 0 {
			// RFC 9001, Section 8.1
			return errors.New("tls: server did not select an ALPN protocol")
		}
		return nil
	}
	if len(clientProtos) == 0 {
		return errors.New("tls: server advertised unrequested ALPN extension")
	}
	for _, proto := range clientProtos {
		if proto == serverProto {
			return nil
		}
	}
	return errors.New("tls: server selected unadvertised ALPN protocol")
}

func (hs *clientHandshakeState) readFinished(out []byte) error {
	c := hs.c

	if err := c.readChangeCipherSpec(); err != nil {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure Config.NextProtos is non-empty and contains the protocol(s) the QUIC server will select (typically "h3" for HTTP/3).
  2. Configure the server to select one of the offered ALPN values.
  3. Verify you are passing the QUIC-mode Config through the QUIC stack correctly.

Example fix

// before: missing ALPN for QUIC
cfg := &tls.Config{}
// after: offer h3 so the QUIC server can select
// (NextProtos is set by the QUIC library; shown for clarity)
cfg := &tls.Config{NextProtos: []string{"h3"}}
Defensive patterns

Strategy: validation

Validate before calling

// For QUIC, ensure Config.NextProtos is non-empty before handing the config to the QUIC stack.
func validateQUICConfig(cfg *tls.Config) error {
    if len(cfg.NextProtos) == 0 {
        return errors.New("QUIC requires at least one ALPN protocol in Config.NextProtos")
    }
    return nil
}

Type guard

func isQUICNoALPN(err error) bool {
    return err != nil && strings.Contains(err.Error(), "server did not select an ALPN protocol")
}

Try / catch

if err := validateQUICConfig(cfg); err != nil { return err }
// (Then perform the QUIC handshake; on isQUICNoALPN, fix server-side ALPN config.)

Prevention

When it happens

Trigger: QUIC handshake via crypto/tls's QUIC mode (Config used with quic:true, e.g. through a QUIC library) where the server did not return an ALPN value; client offered only h3 but the server only knows http/1.1; QUIC server misconfiguration.

Common situations: QUIC server without ALPN configured; offering NextProtos that the server cannot match; using a QUIC library whose Config.NextProtos is empty or wrong.

Understand the failure class

Related errors


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