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 an

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Remove or fix ResponseWriter wrappers so the Hijacker interface propagates (delegate the http.Hijacker methods in wrappers).
  2. 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

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


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/2a587a034b561f6e. Report an issue: GitHub.