tailscale/tailscale · error
failed hijacking conn
Error message
failed hijacking conn
What it means
To bridge a funnel TLS connection, the handler hijacks the underlying TCP conn from the HTTP server via w.(http.Hijacker).Hijack(). If the ResponseWriter does not implement Hijacker or the hijack call fails, the handler returns 500 'failed hijacking conn' and logs it.
Source
Thrown at ipn/ipnlocal/serve.go:1528
if err != nil {
bad("Tailscale-Ingress-Src header invalid; want ip:port")
return
}
target := ipn.HostPort(r.Header.Get("Tailscale-Ingress-Target"))
if target == "" {
bad("Tailscale-Ingress-Target header not set")
return
}
if _, _, err := net.SplitHostPort(string(target)); err != nil {
bad("Tailscale-Ingress-Target header invalid; want host:port")
return
}
getConnOrReset := func() (net.Conn, bool) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
h.logf("ingress: failed hijacking conn")
http.Error(w, "failed hijacking conn", http.StatusInternalServerError)
return nil, false
}
io.WriteString(conn, "HTTP/1.1 101 Switching Protocols\r\n\r\n")
return &ipn.FunnelConn{
Conn: conn,
Src: srcAddr,
Target: target,
}, true
}
sendRST := func() {
http.Error(w, "denied", http.StatusForbidden)
}
h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConnOrReset, sendRST)
}
// wantIngressLocked reports whether this node has ingress configured. This bool
// is sent to the coordination server (in Hostinfo.WireIngress) as anView on GitHub (pinned to 6e0912f979)
Solutions
- Remove or fix ResponseWriter wrappers so the Hijacker interface propagates (delegate the http.Hijacker methods in wrappers).
- Reproduce with stock tailscaled to confirm the wrapper is the cause.
Example fix
// before: middleware wrapper hides optional interfaces
type wrap struct{ http.ResponseWriter }
// after: forward Hijacker so ingress can take over the conn
type wrap struct{ http.ResponseWriter }
func (w *wrap) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := w.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("underlying writer is not a Hijacker")
}
return hj.Hijack()
} Defensive patterns
Strategy: type-guard
Type guard
// Middleware must not break ingress hijacking:
func hijackable(w http.ResponseWriter) bool {
_, ok := w.(http.Hijacker)
return ok
} Prevention
- In any ResponseWriter middleware, explicitly forward http.Hijacker (and http.Flusher) methods.
- Test funnel paths after adding HTTP middleware in custom builds.
When it happens
Trigger: The peerapi HTTP server is wrapped in middleware that replaces ResponseWriter with a non-Hijacker type (custom builds or embedding), or the connection is already torn down when Hijack runs.
Common situations: tsnet or custom servers inserting observability or wrapping middleware in front of the peerapi mux; essentially never happens in stock tailscaled.
Related errors
- only POST allowed
- API response too large
- sending serve config: %w
- tailscale.ACL: %w
- tailscale.ACLHuJSON: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/2a587a034b561f6e.
Report an issue: GitHub.