Limits & timeouts
Every cap in this page is flag-overridable, but the defaults are chosen deliberately — each one guards against a specific failure mode, not a round number picked for its own sake.
HTTP server timeouts#
504. Guards against a hung local app. Post-header response duration is unlimited by design — streaming downloads and SSE need that, and a client disconnect cancels the request context anyway.502 if the session is wedged.Body limits#
Content-Length before the request is proxied at all — this is the real 413, and it covers every well-behaved client.MaxBytesReader for chunked or unknown-length bodies, surfaced as 413 from a custom ErrorHandler that type-checks *http.MaxBytesError.A chunked 413 may ship a partial body
MaxBytesReader alone — returns 502, not 413, because the trip surfaces as a request-body read error inside RoundTrip after headers and up to 256 MiB have already reached the agent. With both checks in place the chunked path still means the agent may have already forwarded a partial body to the local app before the edge cuts it off.Rate limiting#
429.:4443.Concurrency & the window budget#
429.AcceptBacklog is not the concurrency cap
AcceptBacklog is the depth of yamux's accept queue; when it's full, a new stream gets RST'd — a 502the client can't interpret, instead of the 429 the system intends. Concurrency is enforced by the per-session (48) and global (64) semaphores instead, with AcceptBacklog set comfortably above both (512) so it never becomes the binding constraint. The yamux receive window is itself a buffer — each open stream may hold up to its window of received-but-unread bytes on both machines, so concurrency × window is the number that matters, not either alone.The upgrade budget#
A WebSocket upgrade or an SSE stream would otherwise hold one of the semaphores above for its entire life, starving ordinary HTTP requests behind it. The edge detects a long-lived response — 101 Switching Protocols, or a 200 whose Content-Type is text/event-stream — and moves it onto this separate budget, releasing its global and per-session in-flight slot back to the pool. See WebSockets & SSE for the full picture.
A connection that can't be reclassified because either cap is exhausted is rejected outright with 503 Service Unavailable — it is not silently downgraded or left occupying a request slot — and ngstoned_rejected_upgrade_cap_total increments. ngstoned_active_upgrades is a live gauge of currently open upgraded connections.