DNS still resolves. The remote API answers from another machine. Existing sessions remain healthy, yet a Linux application logs EADDRNOTAVAIL or Cannot assign requested address when it opens another outbound TCP connection. In that pattern, the failed resource can be a local source port rather than the remote service.
For an Internet-domain socket that was not explicitly bound, the Linux connect(2) manual defines EADDRNOTAVAIL as failure to find an available ephemeral port during automatic binding. That receipt is specific enough to investigate, but not broad enough to justify changing every TCP sysctl. First identify the process’s network namespace, source address, destination, connection state, and socket allocation path.
Timing errors can look similar from outside. A downstream client abandoning a slow response belongs to nginx 499 timing evidence, while local connect() failure happens before a new TCP session exists. Keeping those events separate prevents a timeout increase from hiding source-port pressure.
On an unbound TCP socket, connect() lets Linux select a source address from routing and a source port from the active local range. If no eligible port can complete the connection identity, the call fails locally with EADDRNOTAVAIL; no SYN needs to reach the destination.
Explicit binding changes the interpretation. A process that asks for a source IP not present in its network namespace can receive the same errno from bind(). Preserve which system call failed, the destination, and the application timestamp instead of searching only for the English text.
| Receipt in the same interval | Likely owner | Next proof |
|---|---|---|
connect() returns EADDRNOTAVAIL on an unbound socket |
automatic local source-port allocation | active range, reserved ports, namespace-local tuples |
bind() returns EADDRNOTAVAIL for a named source IP |
source address is absent or unavailable | namespace addresses and route selection |
| SYN leaves but no reply returns | path, firewall, remote listener, or translation layer | packet/path evidence and remote logs |
| established request later times out | application or downstream latency contract | correlated request and upstream timing |
This boundary matters because raising file-descriptor limits cannot create source-port identities. Likewise, a reachable remote host does not prove the local kernel can allocate another tuple.
A TCP connection is identified by protocol plus source IP, source port, destination IP, and destination port. With ordinary connect(), Linux can reuse the same local IP-and-port pair for different remote endpoints because the complete tuples remain unique. Pressure may therefore concentrate on one hot destination even when the machine talks to many other services successfully.
Application behavior can reduce that reuse. Code that calls bind(source_ip, 0) before connect() asks Linux to choose a local port before the remote endpoint is known. Cloudflare’s source-port allocation analysis demonstrates why that early two-tuple reservation can turn a destination-aware pool into a much tighter host or namespace constraint.
connect() can reuse one local pair across different remote endpoints; early bind(source, 0) reserves the pair before that distinction exists.Linux network namespaces isolate networking resources including protocol stacks, port numbers, and /proc/net, according to network_namespaces(7). Reading ss and sysctls on the host can miss the namespace where the failing process lives.
Docker bridge-mode containers normally have their own network namespace. By contrast, Docker documents that host network mode shares the host networking namespace. Capture evidence where the process executes, then inspect any host NAT or external gateway as a separate boundary.
Begin with the application receipt. Record the UTC window, process or unit, exact errno, remote hostname/IP/port, and whether existing connections stayed healthy. Avoid restarting the service before collecting its current sockets; a restart can drain pressure and erase the allocation pattern.
date -u +%FT%TZ
sudo journalctl -u YOUR_SERVICE --since '-10 minutes' --no-pager | grep -Ei 'EADDRNOTAVAIL|Cannot assign requested address|connect'
Replace YOUR_SERVICE with the real systemd unit. For a container, use its runtime logs and identify one process PID that belongs to the failing workload. The following read-only receipt enters that PID’s network namespace:
PID=12345
sudo nsenter -t "$PID" -n -- sh -c '
printf "range: "; cat /proc/sys/net/ipv4/ip_local_port_range
printf "reserved: "; cat /proc/sys/net/ipv4/ip_local_reserved_ports
printf "reuse mode: "; cat /proc/sys/net/ipv4/tcp_tw_reuse
ss -s
'
Confirm the PID before use with ps -fp 12345. The kernel documentation lists 32768-60999 as the default ip_local_port_range, but the live namespace value is the operational truth. ip_local_reserved_ports independently removes entries from automatic allocation, so the simple inclusive range is only a theoretical ceiling.
Next preserve a state summary without assuming TIME_WAIT owns the incident:
sudo nsenter -t "$PID" -n -- ss -Htan | awk '{count[$1]++} END {for (state in count) print count[state], state}' | sort -nr
If packet-drop counters or interface evidence rise while the application never logs local allocation failure, move to Linux receive-path packet-drop diagnosis. Socket allocation and packet delivery are different layers.
A large TIME_WAIT total is a demand signal, not a verdict. Group established and closing sockets by remote endpoint inside the same namespace. Concentration against one destination is more useful than comparing every socket on the server with one numeric range.
sudo nsenter -t "$PID" -n -- ss -Htan \
| awk '$1 ~ /^(ESTAB|TIME-WAIT|FIN-WAIT-1|FIN-WAIT-2)$/ {print $1, $5}' \
| sort | uniq -c | sort -nr | head -30
The ss(8) manual supports state and endpoint filtering; validate the column layout on the target distribution before automating this report. Compare at least two short intervals so a burst is not mistaken for a stable pool.
One more boundary sits below the host socket table. Conntrack or a NAT gateway can exhaust its own state or translated-port capacity even when the application’s local range has room. Compare the host result with Linux conntrack capacity evidence and gateway metrics; a healthy local pool does not clear downstream translation.
The safest correction changes the smallest proven owner. Do not combine pool tuning, connection-pool changes, source-address additions, and TIME_WAIT policy in one release; an improvement would then have no attributable cause.
For HTTP, database, cache, and message-broker clients, bounded persistent pools reduce connection creation and TLS handshakes together. Match maximum connections, idle lifetime, request timeout, and retry concurrency to the dependency’s supported contract. Unlimited keep-alive is not the goal; stable reuse with a measured cap is.
Watch failure rate and destination-grouped states during the change. A pool that queues forever can trade port exhaustion for application latency, so acceptance needs both allocation and service-level evidence.
When code must choose a source address but does not need a fixed source port, Linux offers IP_BIND_ADDRESS_NO_PORT. The kernel documentation prefers that per-socket option over the global ip_autobind_reuse sysctl, which may break applications and is reserved for expert use.
Changing this behavior belongs in the application or networking library, with compatibility tests for the deployed kernel. Do not simulate it by enabling broad SO_REUSEADDR assumptions or by patching a global sysctl around code you have not traced.
Before widening ip_local_port_range, inventory reserved ports, listening TCP sockets, unconnected UDP sockets, firewall policy, and orchestration assumptions. Save the original value and stage a temporary change before making persistence decisions.
sysctl net.ipv4.ip_local_port_range
sysctl net.ipv4.ip_local_reserved_ports
sudo ss -Hlnutp
No universal replacement range is safe. IANA defines 49152-65535 as Dynamic/Private ports in its service name and port registry, while Linux exposes a configurable range with a different documented default. Reconcile both with the actual software on the server.
Current kernel documentation gives tcp_tw_reuse a default value of 2, meaning loopback-only reuse, and says it should not be changed without expert advice. Enabling global reuse is not a substitute for finding churn, early bind, a hot destination, or NAT limits.
Against one remote IP-and-port pair, another usable source IP can create another set of unique tuples. That is an architecture change: routing, reverse-path policy, firewall rules, provider address assignment, application binding, and downstream NAT must all preserve the added identity.
When measured concurrency genuinely requires multiple source identities or instances, cloud VPS deployment options can support that design. Scaling replicas behind one translating egress IP may leave the external port ceiling unchanged.
Run a bounded comparison at representative, not maximum, traffic. Keep the destination and request mix stable. Capture the same five signals before and after: connect() error rate, successful connection rate, established sockets, closing-state sockets grouped by destination, and application latency.
Probe traffic deserves its own contract. HAProxy health-check diagnosis helps keep a checker mismatch from being misread as capacity recovery. Confirm real client requests as well as active probes.
Rollback if errors move to another layer, latency rises beyond its objective, reserved/listening port collisions appear, or the dependency receives more concurrency than it supports. Authority also matters: managed versus unmanaged VPS responsibilities clarifies whether the application owner, server operator, or provider controls each change.
For an Internet-domain socket that was not already bound, Linux connect() can return EADDRNOTAVAIL when automatic binding cannot find an eligible ephemeral port. Preserve the failing system call because explicit bind() can use the same errno for an unavailable source address.
No. A high TIME_WAIT count shows connection churn, but exhaustion depends on the network namespace, source address, remote endpoint, reserved ports, and socket allocation behavior. Correlate the count with real connect() failures and group sockets by destination.
Current Linux kernel documentation lists 32768-60999 as the default ip_local_port_range. Distributions, containers, or operators can change it, so read the live value inside the failing process’s network namespace.
Not as a default repair. Current kernel documentation uses mode 2 for loopback-only reuse by default and warns against changing the setting without expert advice. Connection reuse, early-bind behavior, destination concentration, and NAT capacity should be proven first.
Containers with separate network namespaces have isolated port-number and socket views. Containers using Docker host network mode share the host networking namespace, so host and container observations refer to the same stack.
No. A wider range does not fix an unavailable bound source address, an application that reserves ports early, a hot single destination beyond the new capacity, or a downstream NAT limit. It can also collide with reserved or listening ports if changed without inventory.
Source-port capacity is not one server-wide percentage. Close the incident with the failing namespace, source identity, remote endpoint, socket API path, active and reserved ranges, state distribution, chosen correction, rollback trigger, and comparable before/after result.
That record makes the next burst answerable. When EADDRNOTAVAIL disappears because the measured tuple constraint changed—and latency, retries, probes, and downstream translation remain healthy—the repair has evidence instead of folklore.