When a busy server starts refusing new connections but traffic and CPU look normal, the cause is often a quiet resource limit: the ephemeral port range, or the loading of SO_REUSEPORT across worker processes. Both show up as connection errors under load and disappear the moment load drops, which makes them easy to misdiagnose as network faults. This guide walks through measuring both, confirming the cause, and widening the limits without opening your box to abuse.
Confirm ephemeral port exhaustion
A server handling many outbound connections — reverse proxies, scrapers, API clients — consumes ports from the local range. If that range is small, or sockets linger in TIME_WAIT, new outbound connections fail with Cannot assign requested address. Check the range and current usage.
sysctl net.ipv4.ip_local_port_range
ss -s
ss -tan state time-wait | wc -l
A default range of 32768 60999 gives roughly 28,000 ports. Each connection to a remote host must use a unique local port, so a process opening thousands of short-lived connections per minute can exhaust it. Raise the range and enable socket reuse.
Widen the range and reduce TIME_WAIT pressure
# /etc/sysctl.d/99-ports.conf
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 262144
Apply and verify.
sysctl --system
sysctl net.ipv4.ip_local_port_range net.ipv4.tcp_tw_reuse
tcp_tw_reuse=1 lets the kernel recycle TIME_WAIT sockets for new outbound connections when it is safe to do so. Since Linux 4.12 it only applies to outbound connections, so it will not disturb state tracking for inbound traffic.
Match the listen backlog to your concurrency
Kernel restart, overflow behaviour, and the application’s own accept queue all need to line up. If the accept queue overflows at any level, clients see connection resets under burst load.
# /etc/sysctl.d/99-ports.conf
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_abort_on_overflow = 0
Then set the application’s listen backlog to the same order of magnitude. In Nginx that is the backlog parameter on the listen directive; in systemd services, ListenStream carries its own backlog.
| Symptom | Likely cause | Fix |
|---|---|---|
| “Cannot assign requested address” | Ephemeral port exhaustion | Widen ip_local_port_range, enable tw_reuse |
| Connections reset under bursts | Backlog overflow | Raise somaxconn + app backlog |
| Slow new connections, CPU fine | SYN queue drops | Raise tcp_max_syn_backlog |
Reproduce the failure before changing anything
A generator that opens outbound connections in a tight loop reproduces port exhaustion on demand and lets you confirm the fix. Run it, watch the error appear, apply the sysctl change, and run it again.
# crude port exhaustion test: open many short-lived connections
for i in $(seq 1 40000); do
(exec 3<>/dev/tcp/127.0.0.1/80 2>/dev/null && exec 3>&- 3<&-) 2>/dev/null
done
echo "exit $?"
Errors such as Cannot assign requested address during the run mean the local range or TIME_WAIT handling is the constraint. After widening ip_local_port_range and enabling tcp_tw_reuse, the same loop completes cleanly.
Second-order effects to check
- Widening the port range lowers the lowest ephemeral port; make sure no local service is already bound in that space, or raise the lower bound above it.
- Aggressive
tcp_tw_reusedoes not remove the need for correct TCP timestamps — verifynet.ipv4.tcp_timestamps=1before trusting it. - Container network namespaces each have their own ephemeral range; a busy container can exhaust ports while the host looks idle.
SO_REUSEPORT: spreading accepts across workers
A single listener means one accept path for every incoming connection. With any modern Linux and an evented server, SO_REUSEPORT lets each worker bind the same port and get its own accept queue, removing a contention point at high request rates. Nginx enables it per listen socket; check whether your build and config set it.
listen 443 ssl reuseport;
listen 80 reuseport;
Verify the kernel accepted the reuse by inspecting the socket, or simply compare request throughput with and without it under sustained load. Do not enable it behind a proxy that relies on a single connection to reach all workers — pick one model or the other.
Watch the right counters
nstat -az | grep -i -E "listen|syn|overflow"shows dropped SYNs and listen overflows since boot.ss -lntshows per-socket Recv-Q; a persistently non-zero value on a listener means the accept queue is backing up.- Track
net.ipv4.tcp_max_tw_bucketsversus actual TIME_WAIT count — if usage sits at the ceiling, TIME_WAIT sockets are being dropped and you have headroom to widen.
Set limits for the process, not just the kernel
A high-connection process also needs its own file-descriptor limit raised, or it hits EMFILE long before the kernel runs out of ports. Check and raise the systemd unit limit for the service.
# /etc/systemd/system/myapp.service.d/limits.conf
[Service]
LimitNOFILE=65536
Then confirm the running process inherited it: cat /proc/$(pidof myapp)/limits | grep "open files". A widened port range with a low descriptor limit just moves the failure one layer up.
These four settings — port range, backlog, tw_reuse, and reuseport — resolve the majority of connection failures that look like network problems. Test them under real load and watch the counters, not just the throughput number. For environment sizing, see the cloud VPS benefits overview, and browse VPS tutorials for related kernel tuning guides.


Leave a Reply
You must be logged in to post a comment.