Quick Summary: Debug EADDRNOTAVAIL on Node.js with Alpine Linux under high load. Uncover the obscure kernel interaction with tcp_tw_reuse and tcp_timestamps. Ess...
You've hit it. That soul-crushing EADDRNOTAVAIL error. Your application, humming along, suddenly chokes on an outbound connection. Instinct screams "port exhaustion!" You checked netstat, you upped the ephemeral port range, you even sacrificed a goat to the TCP gods. Still, it persists. Intermittent, infuriating, and always at the worst possible moment. Welcome to my nightmare, and now, your solution.
This isn't your grandma's port exhaustion. This is a subtle, insidious beast that thrives in the high-churn, short-lived connection environments common to modern microservices. Your Node.js service, especially one making a high volume of external HTTP calls, will periodically spit out Error: connect EADDRNOTAVAIL. It happens just often enough to be a problem, but not consistently enough to be easy to diagnose.
We've all been there: increasing net.ipv4.ip_local_port_range to ludicrous levels. Checking netstat -tanp | grep TIME_WAIT, only to find a perfectly reasonable number of sockets. Ensuring net.ipv4.tcp_tw_reuse is enabled, because that should solve everything, right? You scratch your head. You blame the cloud provider. You blame Node.js. You even suspect that new intern. None of it truly helps, and the errors keep coming, like a dripping faucet in your production logs. This particular flavor of problem can drive an SRE mad.
This gnarly beast predominantly rears its ugly head in a very specific cocktail of environments:
| Component | Affected Versions (Triggers Error) | Unaffected/Mitigated Versions (No Error) |
|---|---|---|
| Operating System | Alpine Linux 3.12 - 3.16 Kernel 5.4.x - 5.10.x |
Alpine Linux >= 3.17 Kernel >= 5.11.x (often has tcp_timestamps enabled by default or improved heuristics) |
| Node.js Runtime | Node.js 14.x, 16.x, 18.x (any version making many short-lived outbound connections) | Node.js 20.x+ (though the kernel issue is primary, newer Node might handle retries better) |
| Network Configuration | net.ipv4.tcp_tw_reuse=1 (explicitly set)net.ipv4.tcp_timestamps=0 (default or explicitly set) |
net.ipv4.tcp_timestamps=1 |
| Workload Pattern | High volume of short-lived outbound HTTP/S connections (e.g., API gateways, microservice calls, particularly in hot loops). | Long-lived connections, lower throughput. |
Months of scratching our heads went into this one. Log after log. Profiler after profiler. The issue was always subtle, just under the radar, especially in our high-throughput algorithmic trading platform where every millisecond counts. This wasn't some basic configuration oversight; this was a deep-seated kernel interaction. It's the kind of problem that makes you question your life choices, reminiscent of other deep dives into OS-level networking quirks like ECONNRESET on HTTP/2 with tcp_tw_reuse.
The Root Cause
EADDRNOTAVAIL literally means "Cannot assign requested address." While often associated with running out of ephemeral ports, in this scenario, it's a false flag. You do have available ports, but the kernel, in its infinite wisdom, refuses to assign them for reuse. Why?
Even with net.ipv4.tcp_tw_reuse=1 enabled, the Linux kernel needs a robust mechanism to safely reuse sockets stuck in the TIME_WAIT state. Without this mechanism, reusing a port too quickly could lead to accepting delayed segments from a previous incarnation of the connection, causing data corruption or protocol confusion. This is where net.ipv4.tcp_timestamps (an implementation of RFC 1323) becomes critical.
When tcp_timestamps is enabled, the kernel adds a timestamp option to TCP segments. This allows it to perform PAWS (Protection Against Wrapped Sequence Numbers) checks. More importantly for our issue, when tcp_tw_reuse is enabled, the kernel relies on these timestamps to determine if a port in TIME_WAIT state can be safely reused for a new outbound connection. Without tcp_timestamps=1, the kernel lacks this critical piece of information. On specific kernel versions (especially the 5.4.x - 5.10.x range found in older Alpine builds), tcp_tw_reuse becomes significantly less effective, or even completely inactive for client-side ephemeral port reuse, if tcp_timestamps is disabled.
The kernel *could* find an ephemeral port. But it deems that port "unsafe" for immediate reuse because it cannot validate its state with a timestamp. Instead of waiting for the full TIME_WAIT period to elapse (which it's supposed to bypass with tcp_tw_reuse), it throws EADDRNOTAVAIL, essentially saying, "I can't find a safe address right now." This is particularly nasty in systems requiring sub-microsecond supremacy in API architectures where connection churn is immense.
The fix, once identified, is deceptively simple and infuriatingly obscure.
You need to enable net.ipv4.tcp_timestamps.
# To make the change persistent across reboots, add this line
# to /etc/sysctl.d/99-network-tweaks.conf (or /etc/sysctl.conf)
# then run: sysctl -p
net.ipv4.tcp_timestamps = 1
# For an immediate, non-persistent change:
sysctl -w net.ipv4.tcp_timestamps=1
This tiny configuration change unlocks the full potential of tcp_tw_reuse. With timestamps enabled, the kernel can confidently and safely recycle client-side ephemeral ports much faster. This dramatically increases the effective pool of available ports, eliminating the bogus EADDRNOTAVAIL errors under high connection churn. Suddenly, your Node.js application can make thousands of rapid outbound connections without breaking a sweat.
It's a frustrating reminder that even with modern runtimes and containerization, the underlying OS kernel can hide the most insidious, high-impact bugs. Dig deep, understand the network stack, and never trust a default. Now go fix your service, and get some sleep. You've earned it.