The number 40ms is a giant hint: it's the classic Nagle + delayed-ACK deadlock. A strong answer doesn't jump to that — it shows the discipline of measuring first, then identifying the exact mechanism the delay points to.
Step 1 — scope the hang with measurements, not guesses. Fire the request from a fresh connection and check: does it happen on the first request only, or every request? Is it 40ms on both loopback and remote? If it reproduces on loopback, it's not the network — it's the stack or the app. Then grab a packet capture (tcpdump/wireshark on the client; tcpdump on the server) and look for the gap.
Step 2 — map 40ms to a mechanism. 40ms is suspiciously close to delayed-ACK behavior. On Linux, delayed ACK fires after ~40ms (the delayed_ack timer is 40ms in the current kernel; historically 40-200ms in the BSD-derived default). The classic interaction:
- Client sends a small request in two segments (or one tiny segment with data pending).
- Nagle holds back the second segment because there's an unACKed small segment in flight.
- The server delays its ACK (up to 40ms) waiting to piggyback it on a response.
- Nothing arrives in the interim, so the ACK waits the full 40ms — and the client, blocked by Nagle, waits for that ACK before sending anything else.
Result: a 40ms pause that shows up as a gap between the server's delayed ACK and the client's next segment. The tell in the capture: you see the request segment, then a 40ms silence, then the delayed ACK, then the rest of the request.
Step 3 — confirm, then fix at the right layer. If the capture shows the pattern, the fixes are: disable Nagle on the client (TCP_NODELAY), or disable delayed ACKs on the server (TCP_QUICKACK) — though for typical HTTP you'd never write one small segment per request, so the real fix is usually coalescing writes or keep-alive reuse. If the gap sits inside the server — between request arrival and response — it's an application timer (a framework's 40ms poll, a GC pause), and the fix is server-side.
Edge cases and traps: if the delay is closer to 200ms, suspect a dropped segment sitting on the retransmission timer (Linux's initial RTO is 200ms), not Nagle. If it's on a mobile client, suspect the radio's active/idle state machine — some carriers idle the modem on ~40ms boundaries. And always check whether the hang is per-request or per-connection: the Nagle/delayed-ACK dance only happens when small segments leave immediately after a previous one, so a first request on a cold connection often shows it while keep-alive traffic doesn't.