tailscale/tailscale · error
hijacking client connection: %w
Error message
hijacking client connection: %w
What it means
After writing the 101 and calling ResponseWriter.Hijack() to take over the raw connection for the Noise handshake, Hijack returned an error. The earlier branch already handles ResponseWriters that don't implement http.Hijacker at all (e.g. http2); this error means Hijack exists but failed — connection already hijacked/closed, middleware wrappers, or a conn in a bad state.
Source
Thrown at control/controlhttp/controlhttpserver/controlhttpserver.go:84
init, err := base64.StdEncoding.DecodeString(initB64)
if err != nil {
http.Error(w, "invalid tailscale handshake header", http.StatusBadRequest)
return nil, fmt.Errorf("decoding base64 handshake header: %v", err)
}
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
return nil, errors.New("can't hijack client connection")
}
w.Header().Set("Upgrade", controlhttpcommon.UpgradeHeaderValue)
w.Header().Set("Connection", "upgrade")
w.WriteHeader(http.StatusSwitchingProtocols)
conn, brw, err := hijacker.Hijack()
if err != nil {
return nil, fmt.Errorf("hijacking client connection: %w", err)
}
defer func() {
if retErr != nil {
conn.Close()
}
}()
if err := brw.Flush(); err != nil {
return nil, fmt.Errorf("flushing hijacked HTTP buffer: %w", err)
}
conn = netutil.NewDrainBufConn(conn, brw.Reader)
cwc := newWriteCorkingConn(conn)
nc, err := controlbase.Server(ctx, cwc, private, init)
if err != nil {
return nil, fmt.Errorf("noise handshake failed: %w", err)View on GitHub (pinned to 0fd2f14deb)
Solutions
- Serve the control endpoint on a plain HTTP/1.1 listener without h2c
- Remove or reorder middleware that wraps or hijacks the ResponseWriter before acceptHTTP
- Ensure the handler runs exactly once per connection
Example fix
// before
srv := &http.Server{Handler: h2cHandler(ctrlHandler), Addr: ":443"} // http2 conn -> Hijack fails
// after
srv := &http.Server{Handler: ctrlHandler, Addr: ":443"} // HTTP/1.1 only Defensive patterns
Strategy: validation
Validate before calling
// Reject anything that is not HTTP/1.x before it reaches acceptHTTP
if r.ProtoMajor != 1 {
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
return
} Prevention
- Serve the control endpoint from a plain HTTP/1.1 server; no h2c
- Unwrap ResponseWriters in middleware or pass Hijacker through intact
When it happens
Trigger: Serving the control endpoint over h2c/HTTP/2 where hijack semantics break; middleware that already consumed or hijacked the conn; double handling of the same request; ResponseWriter wrapped by logging/tracing middleware that breaks the Hijacker assertion chain.
Common situations: golang.org/x/net/http2 h2c servers, custom middleware stacks, tests with fake ResponseWriters, grpc-gateway style wrappers.
Related errors
- can't hijack client connection
- error hijacking connection: %w
- CONNECT hijack unavailable
- CONNECT hijack unavailable
- httptrace didn't provide a connection
AI-assisted analysis of tailscale/tailscale@0fd2f14deb (2026-08-18).
Data as JSON: /api/errors/d35c14df1378b431.
Report an issue: GitHub.