{"record":{"id":"bd74d8445f57d99e","repo":"gorilla/websocket","slug":"websocket-bad-handshake","errorCode":null,"errorMessage":"websocket: bad handshake","messagePattern":"websocket: bad handshake","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client.go","lineNumber":24,"sourceCode":"\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptrace\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\n// ErrBadHandshake is returned when the server response to opening handshake is\n// invalid.\nvar ErrBadHandshake = errors.New(\"websocket: bad handshake\")\n\nvar errInvalidCompression = errors.New(\"websocket: invalid compression negotiation\")\n\n// NewClient creates a new client connection using the given net connection.\n// The URL u specifies the host and request URI. Use requestHeader to specify\n// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies\n// (Cookie). Use the response.Header to get the selected subprotocol\n// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).\n//\n// If the WebSocket handshake fails, ErrBadHandshake is returned along with a\n// non-nil *http.Response so that callers can handle redirects, authentication,\n// etc.\n//\n// Deprecated: Use Dialer instead.\nfunc NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {\n\td := Dialer{\n\t\tReadBufferSize:  readBufSize,\n\t\tWriteBufferSize: writeBufSize,","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/gorilla/websocket/blob/e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f/client.go#L6-L42","documentation":"ErrBadHandshake is the sentinel error returned by Dialer.Dial/DialContext when the server's response to the WebSocket opening handshake is invalid — typically a non-101 HTTP status. The actual HTTP response is returned alongside so callers can inspect it. It wraps any deviation from a valid server handshake response.","triggerScenarios":"Calling Dialer.Dial/DialContext against a server that does not complete the WebSocket upgrade: the endpoint returns 200/404/403/500 instead of 101 Switching Protocols, or the 101 response lacks required headers (Upgrade: websocket, Connection: Upgrade, valid Sec-WebSocket-Accept).","commonSituations":"Pointing the dialer at a plain HTTP REST endpoint, a reverse proxy or API gateway that strips Upgrade headers, missing auth leading to a 401/403 response page, misconfigured TLS terminating proxy, or server not supporting WebSockets on that path.","solutions":["Check the returned *http.Response (its StatusCode and Body) to see why the handshake failed and fix the server or URL","Verify the URL uses ws:// or wss:// and the path actually performs the WebSocket upgrade server-side","Ensure any proxy/load balancer in front supports and forwards Upgrade/Connection headers","If a non-101 status is expected (e.g. 401), handle auth before dialing (header, cookie, or ticket)"],"exampleFix":"// before\nconn, resp, err := dialer.Dial(\"wss://api.example.com/socket\", nil)\nif err != nil { log.Fatal(err) }\n// after\nconn, resp, err := dialer.Dial(\"wss://api.example.com/socket\", nil)\nif errors.Is(err, websocket.ErrBadHandshake) {\n    log.Printf(\"handshake failed: status=%d body=%s\", resp.StatusCode, readBody(resp.Body))\n    return\n}\nif err != nil { log.Fatal(err) }","handlingStrategy":"type-guard","validationCode":"u, err := url.Parse(wsURL)\nif err != nil || (u.Scheme != \"ws\" && u.Scheme != \"wss\") {\n    return fmt.Errorf(\"invalid websocket url: %q\", wsURL)\n}","typeGuard":"func isBadHandshake(err error) bool {\n    return errors.Is(err, websocket.ErrBadHandshake)\n}","tryCatchPattern":"conn, resp, err := dialer.DialContext(ctx, wsURL, nil)\nif err != nil {\n    if errors.Is(err, websocket.ErrBadHandshake) {\n        // inspect resp.StatusCode / body to decide whether to retry or abort\n        return nil, fmt.Errorf(\"handshake rejected: status=%d\", resp.StatusCode)\n    }\n    return nil, err // transport-level error: safe to retry with backoff\n}","preventionTips":["Always check resp.StatusCode when ErrBadHandshake is returned","Confirm the server path actually upgrades connections before dialing in production","Test through the full proxy chain (nginx, ALB) since proxies often break Upgrade headers","Distinguish handshake errors (don't retry) from network errors (retry with backoff)"],"tags":["websocket","handshake","network"],"backgroundTag":"websocket-handshake-failed","analyzedSha":"e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f","analyzedAt":"2026-08-31T12:40:58.222Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}