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
- Confirm the sender is a real DERP router sending a DERPAdmitClientRequest (a few hundred bytes)
- Check for intermediary proxies that might inflate or duplicate the request body
- 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
- Never send anything but a marshalled DERPAdmitClientRequest to /verify
- Monitor 413s on /verify as a signal of misuse or a broken intermediary
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
- Bad Request: invalid JSON
- health check timed out
- empty auth key in response
- MOCKOIDC_CLIENT_ID not defined
- command aborted by user
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/7b5f491aebfd12c1.
Report an issue: GitHub.