Why Linux Reports EADDRNOTAVAIL During Outbound Connection Bursts shown as constrained outbound source-port allocation.
Last edited on August 4, 2026

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.

EADDRNOTAVAIL is a local allocation result

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 source port belongs to a tuple, not a global counter

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.

Automatic connect allocation compared with early bind allocationThe upper rail shows one local source address and port reaching two different remote endpoints because the complete TCP tuples differ. The lower rail shows bind with port zero reserving a local source port before the destination is known, reducing reuse.connect() knows the remote first10.0.0.5:41000API A:443API B:443local pairis reusedbind(source, 0) reserves before connect()bind(10.0.0.5, 0):41000 reserved
Automatic connect() can reuse one local pair across different remote endpoints; early bind(source, 0) reserves the pair before that distinction exists.

Containers change the observation point

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.

Capture the failure inside the owning network namespace

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.

Group sockets by destination before counting TIME_WAIT

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.

Choose the fix that removes the real constraint

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.

Reuse connections before creating more port churn

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.

Fix early binding in the application path

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.

Expand the range only after collision review

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.

Add source identity only when routing preserves it

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.

Prove capacity without hiding another failure

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.

FAQ: Linux source-port exhaustion decisions

What does EADDRNOTAVAIL mean for an unbound TCP connect() call?

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.

Does a high TIME_WAIT count prove ephemeral port exhaustion?

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.

What is the default Linux ephemeral port range?

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.

Should tcp_tw_reuse be enabled globally to free ports faster?

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.

Do Docker containers have separate ephemeral port pools?

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.

Will widening ip_local_port_range always stop EADDRNOTAVAIL?

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.

End with a tuple-aware incident record

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.

Leave a Reply

Your email address will not be published. Required fields are marked *