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:
- One rule for standard web traffic (routing to the backend over HTTP/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).
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
h2andhttp/1.1ALPN protocols. This connector serves both a classic web application and WebSocket endpoints.Client──► (HTTP/2) ──►Envoy Gateway──► (HTTP/2) ──►BackendClient──► (HTTP/2) ──►Envoy Gateway──► (HTTP/1.1) ──►BackendAlternatives Tried
1. Splitting HTTPRoute into 2 rules and binding using
BackendCRD withgateway.envoyproxy.io/wssAn attempt was made to use the custom
Backendresource to specify the WebSocket protocol:502protocol error. Additionally, enabling and managing the customBackendAPI introduces extra configuration overhead and security considerations that are preferable to avoid.2. Splitting HTTPRoute into 2 rules with
useClientProtocol(Successful Workaround)The
HTTPRoutewas split into two separate rules:/wspaths withBackendTrafficPolicyconfigured withuseClientProtocol: true, ensuring the client initiates the WebSocket connection over HTTP/1.1.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
appProtocolIn 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[].appProtocolfield Service/Application protocol.Support for standard or well-defined
appProtocolvalues on the Kubernetes Service to control this behavior would be highly beneficial:This avoids the need to enable the custom
BackendCRD and aligns with standard Kubernetes service definitions.Option B: Dedicated field in
BackendTrafficPolicyProvide an explicit configuration field in
BackendTrafficPolicyto 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).