{"record":{"id":"18a2d935704356b5","repo":"kubernetes/kubernetes","slug":"unable-to-upgrade-missing-upgrade-headers-in-requ","errorCode":null,"errorMessage":"unable to upgrade: missing upgrade headers in request: %#v","messagePattern":"unable to upgrade: missing upgrade headers in request: %#v","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go","lineNumber":91,"sourceCode":"// is capable of upgrading HTTP responses using SPDY/3.1 via the spdystream\n// package.\n//\n// If pingPeriod is non-zero, for each incoming connection a background\n// goroutine will send periodic Ping frames to the server. Use this to keep\n// idle connections through certain load balancers alive longer.\nfunc NewResponseUpgraderWithPings(pingPeriod time.Duration) httpstream.ResponseUpgrader {\n\treturn responseUpgrader{pingPeriod: pingPeriod}\n}\n\n// UpgradeResponse upgrades an HTTP response to one that supports multiplexed\n// streams. newStreamHandler will be called synchronously whenever the\n// other end of the upgraded connection creates a new stream.\nfunc (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {\n\tconnectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))\n\tupgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))\n\tif !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {\n\t\terrorMsg := fmt.Sprintf(\"unable to upgrade: missing upgrade headers in request: %#v\", req.Header)\n\t\thttp.Error(w, errorMsg, http.StatusBadRequest)\n\t\treturn nil\n\t}\n\n\thijacker, ok := w.(http.Hijacker)\n\tif !ok {\n\t\terrorMsg := \"unable to upgrade: unable to hijack response\"\n\t\thttp.Error(w, errorMsg, http.StatusInternalServerError)\n\t\treturn nil\n\t}\n\n\tw.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)\n\tw.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)\n\tw.WriteHeader(http.StatusSwitchingProtocols)\n\n\tconn, bufrw, err := hijacker.Hijack()\n\tif err != nil {\n\t\truntime.HandleErrorWithContext(req.Context(), err, \"Unable to upgrade: error hijacking response\")\n\t\treturn nil","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/kubernetes/kubernetes/blob/b882c60b4023bdf09264c2d5d30a2cadebc240fb/staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go#L73-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: proxy strips hop-by-hop headers (Connection, Upgrade) -> 400\n# nginx default can drop these\n\n# after: explicitly forward the upgrade headers\nlocation / {\n    proxy_pass https://kubelet;\n    proxy_set_header Connection $http_connection;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_http_version 1.1;\n}\n\n# client side: ensure both upgrade headers are sent\nreq.Header.Set(\"Connection\", \"Upgrade\")\nreq.Header.Set(\"Upgrade\", \"SPDY/3.1\")\nreq.Header.Set(\"X-Stream-Protocol-Version\", \"v4.channel.k8s.io\")","handlingStrategy":"validation","validationCode":"// Client: set both upgrade headers required by the SPDY upgrader.\nreq.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade)\nreq.Header.Set(httpstream.HeaderUpgrade, spdy.HeaderSpdy31) // \"SPDY/3.1\"\nreq.Header.Set(httpstream.HeaderProtocolVersion, \"v4.channel.k8s.io\")\n// And configure intermediaries to forward Connection + Upgrade.\n// nginx:\n//   proxy_set_header Connection $http_connection;\n//   proxy_set_header Upgrade $http_upgrade;\n//   proxy_http_version 1.1;","typeGuard":"func isSpdyUpgradeRequest(r *http.Request) bool {\n    conn := strings.ToLower(r.Header.Get(httpstream.HeaderConnection))\n    up := strings.ToLower(r.Header.Get(httpstream.HeaderUpgrade))\n    return strings.Contains(conn, strings.ToLower(httpstream.HeaderUpgrade)) &&\n        strings.Contains(up, strings.ToLower(spdy.HeaderSpdy31))\n}","tryCatchPattern":"// Client: 400 'missing upgrade headers' -> a hop stripped them; reconfigure the proxy and retry.\nif resp.StatusCode == 400 && strings.Contains(body, \"missing upgrade headers\") {\n    return fmt.Errorf(\"intermediary stripped Connection/Upgrade; fix proxy config and retry\")\n}","preventionTips":["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."],"tags":["httpstream","spdy","streaming","upgrade","header","validation","go"],"analyzedSha":"b882c60b4023bdf09264c2d5d30a2cadebc240fb","analyzedAt":"2026-08-07T04:07:48.144Z","schemaVersion":2},"datasetVersion":"2026-08-07T07:17:06.508Z"}