Notice:
This is the "latest" release of Envoy Gateway, which contains the most recent commits from the main branch.
This release might not be stable.
Please refer to the /docs documentation for the most current information.

FAQ

Answers to recurring questions from GitHub issues and community discussions. If your question isn’t covered here, check the rest of the Troubleshooting section or open a GitHub issue.

Why does kubectl exec/port-forward fail when exposing kube-apiserver through Envoy Gateway?

You’ve routed the Kubernetes API server through Envoy Gateway, and plain calls work fine (kubectl get, list, watch), but kubectl exec, attach, or port-forward fail with:

error: unable to upgrade connection: empty server response

The Envoy Gateway access log for the failing request shows a protocol error followed by a rejected upgrade:

{ "method": "GET", "protocol": "HTTP/1.1", "response_code": 502, "response_code_details": "upstream_reset_before_response_started{protocol_error}", "response_flags": "UPE" }
{ "method": "POST", "protocol": "HTTP/1.1", "response_code": 403, "response_code_details": "upgrade_failed" }

Root cause: exec, attach, and port-forward aren’t ordinary request/response calls — they need a bidirectional streaming channel, which kubectl gets by asking the server to switch protocols mid-connection (the SPDY/3.1 or websocket upgrade). Protocol upgrade is an HTTP/1.1-only handshake (a 101 Switching Protocols response). If the connection between Envoy Gateway and kube-apiserver negotiates HTTP/2 instead, that upgrade request has nowhere to go and the request resets. get/list/watch don’t ask for an upgrade, so HTTP/2 handles them without issue.

There are two ways to fix this:

  1. Match the upstream protocol to the client’s, on the route (recommended)

    Attach a BackendTrafficPolicy with useClientProtocol: true and an explicit httpUpgrade list. Envoy then negotiates the upstream connection per-request to match what the client asked for — ordinary calls still get HTTP/2, and upgrade requests get the HTTP/1.1 handshake they need.

    apiVersion: gateway.envoyproxy.io/v1alpha1
    kind: BackendTrafficPolicy
    metadata:
      name: kube-api
      namespace: default
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: HTTPRoute
          name: kube-api
      httpUpgrade:
        - type: "spdy/3.1"
        - type: "websocket"
      useClientProtocol: true
    

    httpUpgrade must name every subprotocol you need passed through — kubectl has used both spdy/3.1 (older versions) and websocket (current versions) across its history, so list both unless you control every client’s kubectl version.

  2. Use a TLSRoute instead of an HTTPRoute

    Passing the connection through at L4 avoids terminating TLS (and negotiating HTTP/2) at the gateway at all. This only works if kube-apiserver’s own certificate is valid for the hostname your clients connect to — on a managed control plane, that certificate usually belongs to the cloud provider and won’t match a custom domain.

Why does kubectl logs -f fail or get cut off after a while?

This is a separate issue with the same symptom family: a long-lived streaming response getting cut off. HTTPRoute rules apply a default request timeout, which eventually terminates a follow-mode log stream. Set an explicit, unbounded timeout on the route rule:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: kube-api
  namespace: default
spec:
  rules:
    - backendRefs: [...]
      timeouts:
        request: 0s

Last modified September 8, 2026: fix (ac3534f92)