labstack/echo · error

proxy raw, hijack error=%w, url=%s

Error message

proxy raw, hijack error=%w, url=%s

What it means

Stored in the request context (_error) by proxyRaw() when http.NewResponseController(w).Hijack() fails while proxying a WebSocket (or other upgraded) connection. Hijacking takes raw control of the underlying TCP connection; if the ResponseWriter (or its wrapped chain) does not implement http.Hijacker, the ResponseController returns ErrNotSupported and Echo records this error. This is a runtime per-request failure, not a startup error.

Source

Thrown at middleware/proxy.go:148

func proxyRaw(c *echo.Context, t *ProxyTarget, config ProxyConfig) http.Handler {
	var dialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
	if transport, ok := config.Transport.(*http.Transport); ok {
		if transport.TLSClientConfig != nil {
			d := tls.Dialer{
				Config: transport.TLSClientConfig,
			}
			dialFunc = d.DialContext
		}
	}
	if dialFunc == nil {
		var d net.Dialer
		dialFunc = d.DialContext
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		in, _, err := http.NewResponseController(w).Hijack()
		if err != nil {
			c.Set("_error", fmt.Errorf("proxy raw, hijack error=%w, url=%s", err, t.URL))
			return
		}
		defer in.Close()

		out, err := dialFunc(c.Request().Context(), "tcp", t.URL.Host)
		if err != nil {
			c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", err, t.URL)))
			return
		}
		defer out.Close()

		// Write header
		err = r.Write(out)
		if err != nil {
			c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", err, t.URL)))
			return
		}

View on GitHub (pinned to 05489dc173)

Solutions

  1. Ensure the ResponseWriter chain reaching the proxy implements http.Hijacker (echo.Response does by default).
  2. In tests, use a real http.Server connection or a writer that embeds/forwards Hijack instead of httptest.ResponseRecorder.
  3. Avoid inserting custom middleware that replaces the Response with a non-Hijacker type before the proxy runs.

Example fix

// before: test uses non-hijackable recorder
rec := httptest.NewRecorder()
proxy.ServeHTTP(rec, req)
// after: use a server with a real connection, or a hijackable writer
srv := httptest.NewServer(proxy)
defer srv.Close()
// connect a real websocket client to srv.URL
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the response writer supports hijacking before routing websockets through the proxy.
func canHijack(w http.ResponseWriter) bool {
    _, _, err := http.NewResponseController(w).Hijack()
    // We can't actually hijack preemptively; instead check interface satisfaction:
    _ = err
    // Prefer an interface check in non-test code:
    type hijacker interface{ Hijack() (net.Conn, *bufio.ReadWriter, error) }
    _, ok := w.(hijacker)
    return ok
}

Try / catch

// After proxy.ServeHTTP, check the context-stored error.
if errVal, ok := c.Get("_error").(error); ok && errVal != nil {
    if strings.Contains(errVal.Error(), "hijack error") {
        // log and return 502 / downgrade
    }
}

Prevention

When it happens

Trigger: A WebSocket request is routed through the Proxy middleware (c.IsWebSocket() is true at proxy.go:394) but the active ResponseWriter does not support hijacking. This happens with custom Response wrappers, test harnesses using httptest.ResponseRecorder, or middleware that swaps in a non-Hijacker writer upstream of the proxy.

Common situations: Running proxy tests with httptest without a real hijackable connection; wrapping echo.Response with a custom writer that drops the Hijacker interface; reverse-proxying websockets behind a custom server adapter.

Related errors


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/9863da88294055d9.json. Report an issue: GitHub.