Intermittent 400 Bad Request with empty body from HTTPS proxy during concurrent requests (SSE streaming + POST)
Author: xdewxCreated Sep 8, 2026Updated Sep 8, 2026
Environment
- portless
0.15.6 portless proxy start --port 1355 --https --skip-trust+portless run nuxt dev(Nuxt 4 / Nitro)- Client: browser
fetch(HTTP/1.1 keep-alive) via Nuxt's$fetch(ofetch)
Symptom
A POST to the proxied backend intermittently fails with HTTP 400 and an EMPTY response body, even though the exact same request succeeds most of the time. It is not reproducible with plain sequential or parallel requests — it correlates with concurrent traffic: a long-lived SSE stream on the same host (e.g. an AI chat /chat streaming endpoint) is open while the POST fires.
Client error (ofetch FetchError):
FetchError: [POST] "/api/items": 400
statusCode: 400
data: '' // empty body
responseStatus: 400Evidence that the proxy, not the backend, emits the 400
- The backend (Nuxt) handler is wrapped by
defineApiHandler, so every handler error is returned as HTTP 200 with a JSON error body; unauthenticated requests return 401 with a JSON body. The backend cannot produce a real400-with-empty-body. - In failing cases the request never reaches the backend (no backend access log), i.e. the proxy rejects it before forwarding.
- Hitting the backend directly (bypassing portless) never shows this
400; sequential and concurrent POSTs against the proxy (120× keep-alive) all passed with the correct status. proxy.logshows no400/error entry for the failing requests.
Expected behavior
Backend responses (2xx/4xx, including bodies) pass through unchanged. The proxy should never emit a bare 400 with an empty body for a well-formed HTTP request.
Notes / possible cause
- Failure is intermittent and timing-dependent; could not build a deterministic reproducer (120 sequential + concurrent keep-alive POSTs all passed).
- Likely a connection/keep-alive race where Node's http server
clientErrorpath responds400 Bad Requestwith no body on a reused/half-closed connection during concurrent streaming + POST traffic on the HTTPS proxy path.
Best-effort reproduction
- Run a backend with an SSE endpoint and a JSON
POSTendpoint (e.g. AI chat stream + item create). - From a browser, keep the SSE stream open and fire the
POSTrepeatedly/concurrently. - Observe intermittent
400with an empty body on thePOST.
Source: vercel-labs/portless