juanfont/headscale · warning · HTTPError

request body too large

Error message

request body too large

What it means

Returned by handleVerifyRequest when io.ReadAll on the POST body of the /verify endpoint (DERP verify protocol) fails. The 413 status reflects that the request body exceeded the limit enforced via verifyBodyLimit (4 KiB) or that the connection was interrupted mid-read. The DERP router calls /verify to check whether a node key is allowed to connect to a relay.

Source

Thrown at hscontrol/handlers.go:132

		return 0, NewHTTPError(http.StatusBadRequest, "invalid capability version", fmt.Errorf("parsing capability version: %w", err))
	}

	return tailcfg.CapabilityVersion(clientCapabilityVersion), nil
}

// verifyBodyLimit caps the request body for /verify. The DERP verify
// protocol payload ([tailcfg.DERPAdmitClientRequest]) is a few hundred
// bytes; 4 KiB is generous and prevents an unauthenticated client from
// OOMing the public router with arbitrarily large POSTs.
const verifyBodyLimit int64 = 4 * 1024

func (h *Headscale) handleVerifyRequest(
	req *http.Request,
	writer io.Writer,
) error {
	body, err := io.ReadAll(req.Body)
	if err != nil {
		return NewHTTPError(http.StatusRequestEntityTooLarge, "request body too large", fmt.Errorf("reading request body: %w", err))
	}

	var derpAdmitClientRequest tailcfg.DERPAdmitClientRequest
	if err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil { //nolint:noinlineerr
		return NewHTTPError(http.StatusBadRequest, "Bad Request: invalid JSON", fmt.Errorf("parsing DERP client request: %w", err))
	}

	allow := h.state.ListNodes().ContainsFunc(func(n types.NodeView) bool {
		return n.NodeKey() == derpAdmitClientRequest.NodePublic
	})

	resp := &tailcfg.DERPAdmitClientResponse{
		Allow: allow,
	}

	return json.NewEncoder(writer).Encode(resp)
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Confirm the sender is a real DERP router sending a DERPAdmitClientRequest (a few hundred bytes)
  2. Check for intermediary proxies that might inflate or duplicate the request body
  3. If legitimate payloads exceed 4 KiB, review verifyBodyLimit in hscontrol/handlers.go and raise it deliberately
Defensive patterns

Strategy: validation

Validate before calling

// Client side: keep the DERPAdmitClientRequest tiny and marshal it fully before sending
payload, err := json.Marshal(&tailcfg.DERPAdmitClientRequest{NodePublic: nodeKey})
if err != nil {
    return err
}
if len(payload) > 4*1024 {
    return fmt.Errorf("verify payload exceeds 4 KiB limit")
}

Try / catch

resp, err := client.Post(verifyURL, "application/json", bytes.NewReader(payload))
if err != nil {
    return err
}
if resp.StatusCode == http.StatusRequestEntityTooLarge {
    // body exceeded the server's verifyBodyLimit; shrink or inspect the payload
}

Prevention

When it happens

Trigger: A DERP server POSTing a DERPAdmitClientRequest larger than the body limit to /verify, or a client aborting the connection while the body is being read.

Common situations: A misconfigured or malicious actor POSTing large payloads to the public /verify endpoint; network interruptions between DERP router and headscale; a proxy that buffers and re-sends oversized bodies.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/7b5f491aebfd12c1. Report an issue: GitHub.