Skip to content

Support standard Service appProtocol or simplified configuration to force HTTP/1.1 for WebSockets on ALPN-enabled backends #9121

Description

@tk-l2002

Description

When a backend service exposes an ALPN connection supporting both HTTP/2 and HTTP/1.1 (e.g., Jetty 12 serving both standard web traffic and WebSockets), proxying WebSocket connections fails with a 502 - upstream_reset_before_response_started{protocol_error}.

This occurs because Envoy attempts to negotiate HTTP/2 upstream for the WebSocket request.

A clean, standard mechanism is needed to force Envoy Gateway to downgrade WebSocket traffic to HTTP/1.1 upstream while keeping HTTP/2 active for standard web traffic, without relying on non-standard or complex custom APIs.

Use Case / Context

  • Envoy Gateway Version: 1.8
  • Backend: Jetty 12 server exposing a single SSL connector supporting both h2 and http/1.1 ALPN protocols. This connector serves both a classic web application and WebSocket endpoints.
  • Traffic Flow: Client ──► (HTTP/2) ──► Envoy Gateway ──► (HTTP/2) ──► Backend
  • The Issue: While standard web-app traffic is correctly proxied over HTTP/2, WebSocket handshakes fail with:
    502 - upstream_reset_before_response_started{protocol_error}
    
  • The Goal: Force the WebSocket connection to downgrade to HTTP/1.1 upstream:
    Client ──► (HTTP/2) ──► Envoy Gateway ──► (HTTP/1.1) ──► Backend

Alternatives Tried

1. Splitting HTTPRoute into 2 rules and binding using Backend CRD with gateway.envoyproxy.io/wss

An attempt was made to use the custom Backend resource to specify the WebSocket protocol:

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: Backend
metadata:
  name: backend-ws
spec:
  appProtocols: gateway.envoyproxy.io/wss	
  • Result: This still resulted in a 502 protocol error. Additionally, enabling and managing the custom Backend API introduces extra configuration overhead and security considerations that are preferable to avoid.

2. Splitting HTTPRoute into 2 rules with useClientProtocol (Successful Workaround)

The HTTPRoute was split into two separate rules:

  1. One rule for standard web traffic (routing to the backend over HTTP/2).
  2. One rule for /ws paths with BackendTrafficPolicy configured with useClientProtocol: true, ensuring the client initiates the WebSocket connection over HTTP/1.1.
  • Result: This successfully forces HTTP/1.1 upstream for WebSockets while keeping HTTP/2 for standard traffic. However, this relies on client-side behavior.

Proposed Solution

The proposal is to support a standard, declarative way to infer or force the upstream protocol for WebSockets.

Option A: Support Kubernetes Service appProtocol

In modern Kubernetes and Gateway API (e.g., Gateway API 1.5+ BackendRef spec), the backend protocol can be inferred via the standard Kubernetes Service ports[].appProtocol field Service/Application protocol.

Support for standard or well-defined appProtocol values on the Kubernetes Service to control this behavior would be highly beneficial:

apiVersion: v1
kind: Service
metadata:
  name: my-backend
spec:
  ports:
  - name: https
    port: 443
    protocol: TCP
    appProtocol: kubernetes.io/wss

This avoids the need to enable the custom Backend CRD and aligns with standard Kubernetes service definitions.

Option B: Dedicated field in BackendTrafficPolicy

Provide an explicit configuration field in BackendTrafficPolicy to specify the upstream protocol version specifically for upgraded/WebSocket connections (e.g., forcing HTTP/1.1 upstream even if the client or default backend connection uses HTTP/2).

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions