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
- Ensure the ResponseWriter chain reaching the proxy implements http.Hijacker (echo.Response does by default).
- In tests, use a real http.Server connection or a writer that embeds/forwards Hijack instead of httptest.ResponseRecorder.
- 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
- Use httptest.NewServer (real TCP) when testing WebSocket proxying, not ResponseRecorder.
- Do not replace echo.Response with a writer that drops the http.Hijacker interface.
- Confirm any custom server adapter exposes Hijack before enabling the Proxy middleware.
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
- proxy raw, copy body error=%w, url=%s
- ErrInvalidListenerNetwork
- echo proxy middleware requires balancer
- file does not implement io.ReadSeeker
- ResponseWriter does not implement 'Unwrap() http.ResponseWri
AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04).
Data as JSON: /data/errors/9863da88294055d9.json.
Report an issue: GitHub.