golang/go · error

tls: server selected unadvertised ALPN protocol

Error message

tls: server selected unadvertised ALPN protocol

What it means

In checkALPN, after confirming the client offered at least one protocol, the loop checks each client proto against the server's selection. If none match, the server selected a protocol the client never advertised, which violates the ALPN RFC (7301) — the server must pick from the client's offer.

Source

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

// 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 {
		return err
	}

	// finishedMsg is included in the transcript, but not until after we
	// check the client version, since the state before this message was
	// sent is used during verification.
	msg, err := c.readHandshake(nil)
	if err != nil {
		return err
	}
	serverFinished, ok := msg.(*finishedMsg)
	if !ok {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Add the protocol the server will select to Config.NextProtos (e.g. include "http/1.1" alongside "h2").
  2. Align the server's ALPN configuration with the protocols clients offer.
  3. Verify no intermediary is overriding the server's selection.

Example fix

// before: only offering h2, server picks http/1.1
cfg := &tls.Config{NextProtos: []string{"h2"}}
// after: offer both so the server can select either
cfg := &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the protocol the server will select is in your offer list.
func validateALPN(offer, serverWillSelect string) error {
    for _, p := range strings.Split(offer, ",") {
        if p == serverWillSelect { return nil }
    }
    return fmt.Errorf("server will select %q which is not in NextProtos", serverWillSelect)
}

Type guard

func isUnadvertisedALPN(err error) bool {
    return err != nil && strings.Contains(err.Error(), "server selected unadvertised ALPN protocol")
}

Try / catch

if _, err := tls.Dial("tcp", addr, cfg); err != nil {
    if isUnadvertisedALPN(err) {
        // Broaden the offer to include what the server selects.
        cfg.NextProtos = []string{"h2", "http/1.1"}
        _, err = tls.Dial("tcp", addr, cfg)
    }
}

Prevention

When it happens

Trigger: Client offers NextProtos = ["h2"] but the server returns "http/1.1"; mismatched ALPN configuration between client and server; ingress/load balancer selecting a different protocol than the client offered.

Common situations: NextProtos list not matching what the server expects; server misconfigured to always select a fixed protocol; load balancer overriding ALPN.

Understand the failure class

Related errors


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