SigNoz/signoz · error · model.ApiError

couldn't upgrade connection: %w

Error message

couldn't upgrade connection: %w

What it means

This error is returned when the SigNoz query-service fails to promote an incoming HTTP request into a WebSocket connection via gorilla/websocket's Upgrader.Upgrade. The upgrade requires the request to be a valid GET with an Upgrade: websocket header, a matching Sec-WebSocket-Key, and any configured Origin/CORS checks to pass. Any deviation causes Upgrade to return an error, which the handler wraps and returns as a 500 InternalError.

Source

Thrown at pkg/query-service/app/http_handler.go:3778

}

func (aH *APIHandler) GetQueryProgressUpdates(w http.ResponseWriter, r *http.Request) {
	// Upgrade connection to websocket, sending back the requested protocol
	// value for sec-websocket-protocol
	//
	// Since js websocket API doesn't allow setting headers, this header is often
	// used for passing auth tokens. As per websocket spec the connection will only
	// succeed if the requested `Sec-Websocket-Protocol` is sent back as a header
	// in the upgrade response (signifying that the protocol is supported by the server).
	upgradeResponseHeaders := http.Header{}
	requestedProtocol := r.Header.Get("Sec-WebSocket-Protocol")
	if len(requestedProtocol) > 0 {
		upgradeResponseHeaders.Add("Sec-WebSocket-Protocol", requestedProtocol)
	}

	c, err := aH.Upgrader.Upgrade(w, r, upgradeResponseHeaders)
	if err != nil {
		RespondError(w, model.InternalError(fmt.Errorf(
			"couldn't upgrade connection: %w", err,
		)), nil)
		return
	}
	defer c.Close()

	// Websocket upgrade complete. Subscribe to query progress and send updates to client
	//
	// Note: we handle any subscription problems (queryId query param missing or query already complete etc)
	// after the websocket connection upgrade by closing the channel.
	// The other option would be to handle the errors before websocket upgrade by sending an
	// error response instead of the upgrade response, but that leads to a generic websocket
	// connection failure on the client.

	queryId := r.URL.Query().Get("q")

	progressCh, unsubscribe, apiErr := aH.reader.SubscribeToQueryProgress(queryId)
	if apiErr != nil {

View on GitHub (pinned to 5069bf80b0)

Solutions

  1. Verify the client is issuing a proper WebSocket handshake (wss:// URL, browser WebSocket API, or a real WS client) against the correct endpoint
  2. Fix reverse-proxy config to pass through upgrade headers (nginx: proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; and disable buffering/timeouts for the WS route)
  3. If the frontend is served from a different origin, set aH.Upgrader.CheckOrigin to allow the dashboard origin rather than relying on the default same-origin check
  4. If it's a proxy idle-timeout disconnect, increase read/write deadlines on both proxy and server

Example fix

// before (default same-origin CheckOrigin rejects cross-origin dashboards)
c, err := aH.Uppgrader.Upgrade(w, r, upgradeResponseHeaders)

// after
upgrader := websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        origin := r.Header.Get("Origin")
        return origin == "https://app.mycompany.com" // or parse against an allowlist
    },
}
c, err := aH.upgrader.Upgrade(w, r, upgradeResponseHeaders)
Defensive patterns

Strategy: fallback

Validate before calling

// Before opening, verify the endpoint is a WS endpoint and proxy passes headers
const isWsCapable = (url) => url.startsWith('ws://') || url.startsWith('wss://');
if (!isWsCapable(endpoint)) throw new Error('not a websocket url');

Try / catch

try {
  const ws = new WebSocket(url, protocols);
  ws.onerror = () => { /* fall back to HTTP polling */ startPollingFallback(); };
} catch (e) {
  startPollingFallback();
}

Prevention

When it happens

Trigger: GET /api/v1/ws/* (logs live-tail, widgets websocket) with a non-GET method, missing/incorrect Sec-WebSocket-* headers, a client that is not actually speaking WebSocket, or an Origin header rejected by the Upgrader's CheckOrigin policy (e.g., dashboard served from a different domain/port behind a misconfigured reverse proxy).

Common situations: Reverse proxies (nginx/traefik) that strip Upgrade/Connection headers or time out long-lived sockets; frontend served cross-origin while aH.Upgrader uses the default same-origin CheckOrigin; HTTP/1.0 clients or health-checkers probing the WS endpoint with plain GET; proxies buffering or cutting idle connections so the handshake never completes.

Related errors


AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28). Data as JSON: /api/errors/118ed0e3c1d6bcda. Report an issue: GitHub.