cloudflare/cloudflared · error

status not yet written before attempting to hijack connectio

Error message

status not yet written before attempting to hijack connection

What it means

http2RespWriter implements http.Hijacker. Hijacking (taking over the raw connection, e.g. for websocket upgrades) is only legal after the HTTP status has been written. If Hijack is called while rp.statusWritten is false, it returns `status not yet written before attempting to hijack connection` instead of the connection.

Source

Thrown at connection/http2.go:312

}

func (rp *http2RespWriter) WriteHeader(status int) {
	if rp.hijacked() {
		rp.log.Warn().Msg("WriteHeader after hijack")
		return
	}
	_ = rp.WriteRespHeaders(status, rp.respHeaders)
}

func (rp *http2RespWriter) hijacked() bool {
	rp.hijackedMutex.Lock()
	defer rp.hijackedMutex.Unlock()
	return rp.hijackedv
}

func (rp *http2RespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if !rp.statusWritten {
		return nil, nil, fmt.Errorf("status not yet written before attempting to hijack connection")
	}
	// Make sure to flush anything left in the buffer before hijacking
	if rp.shouldFlush {
		rp.flusher.Flush()
	}
	rp.hijackedMutex.Lock()
	defer rp.hijackedMutex.Unlock()
	if rp.hijackedv {
		return nil, nil, http.ErrHijacked
	}
	rp.hijackedv = true
	conn := &localProxyConnection{rp}
	// We return the http2RespWriter here because we want to make sure that we flush after every write
	// otherwise the HTTP2 write buffer waits a few seconds before sending.
	readWriter := bufio.NewReadWriter(
		bufio.NewReader(rp),
		bufio.NewWriter(rp),
	)

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Write the response status (e.g. http.StatusSwitchingProtocols) via WriteHeader before calling Hijack
  2. Verify the origin's websocket handler completes its 101 response through the writer before hijacking
  3. Ensure no early return/short-circuit skips the WriteHeader call in the proxy path
  4. If using a third-party origin library, upgrade it to one that writes status before hijacking

Example fix

// before
conn, rw, _ := respWriter.Hijack()
// after
respWriter.WriteHeader(http.StatusSwitchingProtocols)
conn, rw, err := respWriter.Hijack()
Defensive patterns

Strategy: type-guard

Type guard

func canHijack(w http.ResponseWriter) bool {
    h, ok := w.(http.Hijacker)
    return ok // plus ensure status already written by the caller
}

Try / catch

conn, rw, err := respWriter.Hijack()
if err != nil {
    // status wasn't written yet; write the 101 then retry once
    respWriter.WriteHeader(http.StatusSwitchingProtocols)
    conn, rw, err = respWriter.Hijack()
}

Prevention

When it happens

Trigger: originProxy code (or an origin server library) calls Hijack() on the respWriter before any WriteHeader/Write occurred — typically a websocket or spliced stream handler that skips writing the 101/2xx status first.

Common situations: Origin libraries that hijack immediately on upgrade requests without first writing the switching-protocols response, or custom proxy code reusing cloudflared's respWriter incorrectly.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/213d78c65d90a93c. Report an issue: GitHub.