The Nagle Algorithm and Delayed ACKs: Why Your Small Writes Get 40ms of Latency

2026-08-30

Two independent optimizations, both defaults, both correct in isolation — combined they add a fixed 40ms of latency to your request/response protocol. This is one of the oldest and most persistent performance footguns in Unix networking.

Nagle's algorithm (RFC 896) lives in the TCP sender. Its rule: if there is unacknowledged data in flight AND the pending send is smaller than the MSS, hold it. Send only when either an ACK arrives or a full segment accumulates. This prevented "tinygrams" — telnet keystrokes generating 41-byte packets across 1980s WANs.

Delayed ACK (RFC 1122) lives in the TCP receiver. Its rule: don't ACK immediately; wait up to 40ms in hopes of piggybacking the ACK on outbound data, or coalescing it with the ACK for the next segment. This halves ACK traffic.

The pathological interaction: your client does write(hdr); write(body); where both are sub-MSS. Nagle sends the header immediately (nothing in flight), then holds the body waiting for the ACK. The server's TCP receives the header, delays the ACK waiting for a reply-piggyback. The server's application is blocked in read() waiting for a complete request that will never arrive until it does. Deadlock, broken only when the delayed-ACK timer fires — typically 40ms on Linux, up to 200ms on other stacks.

Real-world example: John Nagle himself documented this in 2015 on Hacker News, noting that a bug report he filed against BSD in 1985 was still causing problems. Redis, memcached, and most modern RPC libraries set TCP_NODELAY by default for exactly this reason. HAProxy defaults it. The Go net package sets it on every TCP socket. If you build a wire protocol and forget it, you will eventually see a bimodal latency histogram with a mode at ~40ms and no clue why.

The fix, in order of preference:

Rule of thumb: if your protocol is request/response and you emit N writes per message with N>1, you need TCP_NODELAY — or a userspace buffer that emits exactly one write/writev per message. Loopback doesn't save you: the interaction happens in the TCP stack, not on the wire.

Key Takeaway: Nagle holds small sends until an ACK arrives; delayed ACK holds ACKs waiting for reply data — combined, they deadlock request/response protocols for 40ms per exchange, which is why TCP_NODELAY is set by default in virtually every modern network library.

All newsletters