Why Fast APIs Crash with 'Cannot Assign Address' (TIME_WAIT)
🎯 The Question
"You deploy a high-performance backend microservice making 5,000 HTTP requests per second to an external payment API. Under load, your service suddenly crashes with the error:
EADDRNOTAVAIL: Cannot assign requested address. Why does closing TCP connections trigger this crash, and what is the TIME_WAIT state?"
⚡ 30-Second Elevator Pitch
Every outgoing TCP connection is uniquely identified by a 4-Tuple:
When your service connects to an external API (api.stripe.com:443), three of those variables are fixed. The Linux kernel must assign an unused Ephemeral Port (by default, ports 32768 to 60999, offering ).
The TIME_WAIT Trap:
- Whichever side actively closes the TCP connection first must stay in the
TIME_WAITstate for (Maximum Segment Lifetime = 60 seconds). - During these 60 seconds, that local port number is permanently locked by the kernel to ensure lingering packets from the old connection do not corrupt future connections.
- At 5,000 req/sec without connection pooling:
- The system exhausts all 28,000 ephemeral ports in under 6 seconds, throwing
Cannot assign requested address!
🧠 Under-the-Hood: Ephemeral Port Exhaustion Pipeline
🔬 How to Fix: HTTP Keep-Alive & Connection Pooling
The primary production solution is to stop creating and destroying TCP connections:
1. Enable HTTP Keep-Alive & Connection Pooling
Reuse existing TCP connections across thousands of requests:
- Node.js: Use
http.Agent({ keepAlive: true, maxSockets: 500 }). - Go:
http.ClientwithTransport.MaxIdleConnsPerHost = 100. - Python: Use
requests.Session()instead of rawrequests.get().
2. Linux Kernel Socket Reuse (tcp_tw_reuse)
Allow the kernel to safely recycle TIME_WAIT sockets for outgoing connections when timestamps match:
sysctl -w net.ipv4.tcp_tw_reuse=1
3. Expand Ephemeral Port Range
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
📌 Comparison Matrix: Unpooled Requests vs. Connection Pooling
| Metric | Without Connection Pool (Short-Lived Sockets) | With HTTP Connection Pool (Persistent Keep-Alive) |
|---|---|---|
| TCP Handshake Overhead | 3-way handshake on every single request | Handshake done once; reused for thousands of requests |
| TLS / SSL Handshake | Expensive crypto negotiation on every call | 0 ms overhead after initial connection |
| Ephemeral Port Churn | Thousands of ports consumed per second | Uses a fixed pool of ~50 to 500 socket ports |
TIME_WAIT Accumulation | Rapidly exhausts port limits (EADDRNOTAVAIL) | Near zero sockets in TIME_WAIT state |
| API Latency | 50–150 ms per request | 2–5 ms per request ( faster!) |
💡 What Interviewers Ask Next (Follow-Up Traps)
-
"Why does TCP mandate the TIME_WAIT state in the first place?"
- Answer: Two critical reasons:
- Reliable connection termination: If the final
ACKfrom the active closer is lost, the peer retransmitsFIN. The closer must be inTIME_WAITto resendACK, rather than responding withRST. - Preventing packet corruption: Lingering duplicate packets delayed in routers must completely die in the network before a new connection reuses the same 4-tuple.
- Reliable connection termination: If the final
- Answer: Two critical reasons:
-
"Why was
net.ipv4.tcp_tw_recycleremoved from recent Linux kernels?"- Answer:
tcp_tw_recycleaggressively recycled sockets based on per-host timestamps. When clients sat behind a NAT gateway (like mobile carriers or corporate firewalls), multiple devices shared one public IP with unsynchronized clock timestamps, causing legitimate connections to be dropped randomly. It was removed in Linux 4.12.
- Answer:
Interview Answer: Cannot assign requested address happens when a client exhausts its ephemeral port pool because closed TCP connections stay locked in the 60-second TIME_WAIT state. To fix this in production, never open raw HTTP connections per request; always use HTTP Keep-Alive connection pools to reuse established TCP sockets.