gorilla/websocket · error · HandshakeError
http.StatusText(status)
Error message
http.StatusText(status)
What it means
returnError is the Upgrader's error path during a failed WebSocket handshake. It writes an HTTP error response (with Sec-Websocket-Version: 13 header) whose body is http.StatusText(status), and returns a HandshakeError carrying the same reason to the caller. The developer sees this when an incoming request fails upgrade validation — the reason string is typically an HTTP status text such as 'Method Not Allowed', 'Bad Request', or 'Unauthorized'.
Source
Thrown at server.go:82
//
// A CheckOrigin function should carefully validate the request origin to
// prevent cross-site request forgery.
CheckOrigin func(r *http.Request) bool
// EnableCompression specify if the server should attempt to negotiate per
// message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
}
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
err := HandshakeError{reason}
if u.Error != nil {
u.Error(w, r, status, err)
} else {
w.Header().Set("Sec-Websocket-Version", "13")
http.Error(w, http.StatusText(status), status)
}
return nil, err
}
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
func checkSameOrigin(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return true
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
return equalASCIIFold(u.Host, r.Host)
}
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {View on GitHub (pinned to e064f32e36)
Solutions
- Read the HandshakeError reason / returned status to see which validation failed (method, headers, version, origin, subprotocol)
- Ensure the client performs a real WS upgrade (use a WebSocket client, not plain fetch/curl without headers: Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key)
- If behind nginx/HAProxy, forward upgrade headers: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"
- Relax or fix CheckOrigin to accept your frontend's Origin, instead of relying on the default same-origin check
- Ensure the client requests version 13 and, if using subprotocols, that the server lists a matching one
Example fix
// before (default same-origin check rejects cross-origin client)
var upgrader = websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil) // err: "websocket: request origin not allowed by Upgrader.CheckOrigin"
// after: accept known origins
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://app.example.com" || origin == ""
},
}
conn, err := upgrader.Upgrade(w, r, nil) Defensive patterns
Strategy: try-catch
Validate before calling
func canUpgrade(r *http.Request) error {
if r.Method != http.MethodGet {
return fmt.Errorf("need GET, got %s", r.Method)
}
if !strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
return errors.New("missing/invalid Upgrade header")
}
if !tokenListContains(r.Header.Get("Connection"), "upgrade") {
return errors.New("missing Connection: Upgrade")
}
if r.Header.Get("Sec-Websocket-Key") == "" {
return errors.New("missing Sec-WebSocket-Key")
}
if r.Header.Get("Sec-Websocket-Version") != "13" {
return errors.New("unsupported websocket version")
}
return nil
} Type guard
func isHandshakeError(err error) bool {
_, ok := err.(websocket.HandshakeError)
return ok
} Try / catch
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
if isHandshakeError(err) {
// Upgrade already wrote the HTTP error response; just log and return
log.Printf("handshake rejected from %s: %v", r.RemoteAddr, err)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
} Prevention
- Always check errors from Upgrade and do not write another response afterwards — it already wrote one
- Set an explicit CheckOrigin matching your deployed origins instead of relying on same-origin defaults
- Configure reverse proxies (nginx/HAProxy/ALB) to forward Upgrade/Connection headers
- Test the endpoint with a real WebSocket client (wscat, browser) not plain HTTP requests
- If you set Upgrader.Error, make sure it writes a response so clients are not left hanging
When it happens
Trigger: Upgrade() rejects a request: non-GET method, missing/invalid Upgrade or Connection headers, missing Sec-WebSocket-Key, unsupported Sec-WebSocket-Version, CheckOrigin returning false, or Subprotocol mismatch — each maps to a status whose StatusText becomes the error reason.
Common situations: Hitting the WS endpoint with a plain browser GET or curl without Upgrade headers; misconfigured reverse proxy/load balancer stripping Upgrade and Connection headers; CheckOrigin rejecting a legitimate cross-origin frontend (different host during development); client sending Sec-WebSocket-Version other than 13; accessing via wrong scheme without TLS termination.
Related errors
- websocket: bad handshake
- websocket: invalid compression negotiation
- malformed ws or wss URL
- websocket: duplicate header not allowed:
- websocket: protocol %q was given but is not supported;sharin
AI-assisted analysis of gorilla/websocket@e064f32e36 (2026-08-31).
Data as JSON: /api/errors/9cda1592160e8466.
Report an issue: GitHub.