XTLS/Xray-core · error · errors.Error

negotiated unsupported application layer protocol: {nextProt

Error message

negotiated unsupported application layer protocol: {nextProto}

What it means

After establishing the transport to the HTTP proxy, the client switches on the negotiated ALPN nextProto. Only "h2" and ""/"http/1.1" are handled; any other negotiated protocol (e.g. "h3", experimental strings from a TLS-terminating middlebox) falls into default and this error is returned.

Source

Thrown at proxy/http/client.go:348

		if err != nil {
			rawConn.Close()
			return nil, err
		}

		cachedH2Mutex.Lock()
		if cachedH2Conns == nil {
			cachedH2Conns = make(map[net.Destination]h2Conn)
		}

		cachedH2Conns[dest] = h2Conn{
			rawConn: rawConn,
			h2Conn:  h2clientConn,
		}
		cachedH2Mutex.Unlock()

		return proxyConn, err
	default:
		return nil, errors.New("negotiated unsupported application layer protocol: " + nextProto)
	}
}

func newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {
	return &http2Conn{Conn: c, in: pipedReqBody, out: respBody}
}

type http2Conn struct {
	net.Conn
	in  *io.PipeWriter
	out io.ReadCloser
}

func (h *http2Conn) Read(p []byte) (n int, err error) {
	return h.out.Read(p)
}

func (h *http2Conn) Write(p []byte) (n int, err error) {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Restrict streamSettings.tlsSettings.alpn to ["h2", "http/1.1"] for the HTTP outbound
  2. Ensure you are actually talking to the HTTP proxy, not a CDN endpoint that negotiates other protocols
  3. Upgrade Xray if a newer build supports additional protocols on this path
  4. Check for TLS-intercepting middleboxes rewriting ALPN

Example fix

// before
"streamSettings": { "tlsSettings": { "alpn": ["h3"] } }
// after
"streamSettings": { "tlsSettings": { "alpn": ["h2", "http/1.1"] } }
Defensive patterns

Strategy: validation

Validate before calling

```go
allowed := map[string]bool{"h2": true, "http/1.1": true, "": true}
if state, ok := tls ConnectionState(); ok && !allowed[state.NegotiatedProtocol] {
    // fix alpn config before the outbound trips the default case
}
```

Type guard

```go
func supportedALPN(proto string) bool {
    return proto == "" || proto == "h2" || proto == "http/1.1"
}
```

Prevention

When it happens

Trigger: settings.servers[].tls enabled (or stream TLS to the proxy) where the TLS endpoint negotiates something other than h2 or http/1.1 — custom ALPN lists in streamSettings.tlsSettings.alpn, h3-capable proxies, or an intercepting box injecting unexpected protocols.

Common situations: User sets tlsSettings.alpn to ["h3"] or ["h2","h3"] for an HTTP outbound; a CDN/LB in front of the proxy negotiating its preferred proto; older Xray builds without some proto support.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/caa0eed8e4bf3263. Report an issue: GitHub.