kubernetes/kubernetes · warning
unable to upgrade: missing upgrade headers in request: %#v
Error message
unable to upgrade: missing upgrade headers in request: %#v
What it means
400 Bad Request returned by spdy.responseUpgrader.UpgradeResponse (upgrade.go:87-93) when the request is not a proper SPDY upgrade: the Connection header does not contain 'Upgrade' OR the Upgrade header does not contain 'SPDY/3.1' (case-insensitive). The full request Header is dumped into the message via %#v for debugging. This runs before any attempt to negotiate the subprotocol or hijack the connection.
Source
Thrown at staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go:91
// is capable of upgrading HTTP responses using SPDY/3.1 via the spdystream
// package.
//
// If pingPeriod is non-zero, for each incoming connection a background
// goroutine will send periodic Ping frames to the server. Use this to keep
// idle connections through certain load balancers alive longer.
func NewResponseUpgraderWithPings(pingPeriod time.Duration) httpstream.ResponseUpgrader {
return responseUpgrader{pingPeriod: pingPeriod}
}
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
// streams. newStreamHandler will be called synchronously whenever the
// other end of the upgraded connection creates a new stream.
func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
errorMsg := fmt.Sprintf("unable to upgrade: missing upgrade headers in request: %#v", req.Header)
http.Error(w, errorMsg, http.StatusBadRequest)
return nil
}
hijacker, ok := w.(http.Hijacker)
if !ok {
errorMsg := "unable to upgrade: unable to hijack response"
http.Error(w, errorMsg, http.StatusInternalServerError)
return nil
}
w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
w.WriteHeader(http.StatusSwitchingProtocols)
conn, bufrw, err := hijacker.Hijack()
if err != nil {
runtime.HandleErrorWithContext(req.Context(), err, "Unable to upgrade: error hijacking response")
return nilView on GitHub (pinned to b882c60b40)
Solutions
- Use kubectl/client-go remotecommand, which sets Connection: Upgrade and Upgrade: SPDY/3.1 correctly for SPDY streaming.
- Configure intermediaries to preserve Connection and Upgrade headers end-to-end (these are hop-by-hop by default and often stripped — explicitly allow-list them).
- Verify the endpoint expects SPDY; some newer paths prefer websockets — ensure the client matches the server's transport.
- Inspect the %#v header dump in the error message to see exactly which headers arrived stripped.
- If running over HTTP/2, fall back to HTTP/1.1 for the streaming call or use a websocket-capable path.
Example fix
# before: proxy strips hop-by-hop headers (Connection, Upgrade) -> 400
# nginx default can drop these
# after: explicitly forward the upgrade headers
location / {
proxy_pass https://kubelet;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
proxy_http_version 1.1;
}
# client side: ensure both upgrade headers are sent
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "SPDY/3.1")
req.Header.Set("X-Stream-Protocol-Version", "v4.channel.k8s.io") Defensive patterns
Strategy: validation
Validate before calling
// Client: set both upgrade headers required by the SPDY upgrader. req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) req.Header.Set(httpstream.HeaderUpgrade, spdy.HeaderSpdy31) // "SPDY/3.1" req.Header.Set(httpstream.HeaderProtocolVersion, "v4.channel.k8s.io") // And configure intermediaries to forward Connection + Upgrade. // nginx: // proxy_set_header Connection $http_connection; // proxy_set_header Upgrade $http_upgrade; // proxy_http_version 1.1;
Type guard
func isSpdyUpgradeRequest(r *http.Request) bool {
conn := strings.ToLower(r.Header.Get(httpstream.HeaderConnection))
up := strings.ToLower(r.Header.Get(httpstream.HeaderUpgrade))
return strings.Contains(conn, strings.ToLower(httpstream.HeaderUpgrade)) &&
strings.Contains(up, strings.ToLower(spdy.HeaderSpdy31))
} Try / catch
// Client: 400 'missing upgrade headers' -> a hop stripped them; reconfigure the proxy and retry.
if resp.StatusCode == 400 && strings.Contains(body, "missing upgrade headers") {
return fmt.Errorf("intermediary stripped Connection/Upgrade; fix proxy config and retry")
} Prevention
- Use client-go remotecommand, which sets the SPDY upgrade headers.
- Explicitly forward hop-by-hop headers (Connection, Upgrade) through every proxy.
- Serve streaming endpoints over HTTP/1.1 where SPDY upgrade is expressible.
- Inspect the %#v header dump in the message to see what actually arrived.
When it happens
Trigger: A request to a SPDY streaming endpoint (exec/attach/port-forward on kubelet/apiserver) that lacks the standard HTTP upgrade headers; a client that set X-Stream-Protocol-Version but forgot Connection: Upgrade / Upgrade: SPDY/3.1; a proxy that strips or rewrites the Connection/Upgrade headers; an HTTP/2 client where upgrade semantics differ.
Common situations: Intermediaries (cloud LBs, ingress, service meshes) that drop the Connection header because it is hop-by-hop per RFC 7230; a websocket client hitting a SPDY-only endpoint; client-go/kubectl version mismatch; an HTTP/2-only path where the SPDY/3.1 upgrade cannot be expressed.
Related errors
- unable to upgrade: header %s does not exist in request with
- unable to upgrade: header %s is empty
- unable to upgrade: header %s contains %s, but no valid proto
- unable to upgrade: header %s does not exist in request with
- unable to upgrade: header %s is empty
AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07).
Data as JSON: /api/errors/18a2d935704356b5.
Report an issue: GitHub.