| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | iproute 6.17.0-2.el10.x86_64podman 5.6.0-6.el10_1.x86_64 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Fedora, Debian, Ubuntu, and other Linux distributions using the standard socket API |
| Privilege | Normal user to inspect addresses and sockets; sudo to change service config, sysctl, or restart daemons |
| Scope | Diagnose and fix Cannot assign requested address (EADDRNOTAVAIL, errno 99) for bind() and connect() failures. Covers wrong local IP, boot ordering, Docker host publish binds, ephemeral port exhaustion, IPv4/IPv6 mismatches, and intentional non-local binds. Does not treat port conflicts (EADDRINUSE) or SELinux denials (EACCES) as primary causes of errno 99. |
| Related guides | ss command sysctl command Check open ports in Linux systemctl command sysctl config for high-performance servers |
Cannot assign requested address is Linux error EADDRNOTAVAIL (errno 99). When an application gets errno 99 from bind(), it usually means it requested a local IP address that is not assigned to the host or an interface that does not exist. When an outbound connect() fails with the same message, Linux may also be unable to allocate an ephemeral source port under connection pressure. The fixes differ, so the first step is deciding which path you are on.
Quick diagnosis: why you get errno 99
| Situation | Most likely cause | First check |
|---|---|---|
| Nginx, Apache, or another daemon fails on startup | Configured listen IP is not local | ip -br addr |
docker run -p IP:PORT:PORT or Podman publish fails |
Host does not own IP |
ip -br addr |
| Works after boot but failed during early boot | Address or interface not ready yet | journalctl -b -u SERVICE, ip -br addr |
| SSH tunnel or local forward reports bind failure | Wrong local IP or IPv4/IPv6 bind mismatch | ssh -vvv, ip -br addr |
| Client, proxy, or API worker fails only under heavy load | Ephemeral ports exhausted | sysctl net.ipv4.ip_local_port_range, ss -s |
| Port already occupied | Different error: normally EADDRINUSE (errno 98) |
ss -lntp — see check open ports |
| SELinux blocks a socket | Different error class: normally EACCES |
audit logs — not a primary errno 99 cause |
bind() failed with errno 99 → check local IP / interface
connect() failed with errno 99 → check ephemeral ports / connection churnWhat does "Cannot assign requested address" mean?
Linux maps EADDRNOTAVAIL to the user-visible string Cannot assign requested address. For bind(), the kernel documents this as an attempt to use an address that is not local or does not exist on an interface on this machine.
That is a different failure from a busy port:
| Errno | Name | Typical message | Meaning |
|---|---|---|---|
| 99 | EADDRNOTAVAIL |
Cannot assign requested address | Local address unavailable for this socket |
| 98 | EADDRINUSE |
Address already in use | Port (or address/port pair) already bound |
Applications surface the same wording in logs and stack traces. Nginx may print bind() to 192.168.1.100:80 failed (99: Cannot assign requested address). Python reports [Errno 99] Cannot assign requested address. Podman and Docker print bind: cannot assign requested address when the host publish address is wrong.
Check whether the IP address exists on the server
This is the main fix for server-side bind() failures. List addresses on every interface:
ip -br addrSample output on a lab host with two interfaces:
lo UNKNOWN 127.0.0.1/8 ::1/128
enp0s3 UP 10.0.2.15/24 fd17:625c:f037:2:a00:27ff:fe2d:cd83/64 fe80::a00:27ff:fe2d:cd83/64
enp0s8 UP 192.168.56.116/24 fe80::71bc:541f:4300:7820/64Suppose a service is configured to listen on 192.168.1.100 but that address does not appear in the output above. The kernel rejects the bind because this host does not own that IP.
Your options:
- Assign the correct address on the intended interface (static config, DHCP reservation, or cloud secondary IP).
- Change the application to a local address that already exists, such as
192.168.56.116in the example. - Listen on all IPv4 interfaces with
0.0.0.0or omit the address and bind only the port — only when exposing the service on every interface matches your security design.
0.0.0.0 is not a universal fix. It changes which interfaces accept traffic and may expose a daemon more widely than a single-IP listen directive.
Fix application bind addresses
The socket rule is the same for Nginx, Apache, MySQL, Redis, SSH, and other daemons: the configured address must exist locally before bind() succeeds.
Nginx and web servers
A common misconfiguration:
server {
listen 192.168.1.100:80;
server_name example.com;
}Compare 192.168.1.100 against ip -br addr. If it is missing, set listen to a local IP or to port-only form when appropriate:
server {
listen 80;
server_name example.com;
}Validate syntax before reload:
sudo nginx -tThen restart or reload through systemctl. Apache uses Listen in the same way; databases use bind-address (MySQL) or bind (Redis). SSH uses ListenAddress in /etc/ssh/sshd_config. The file path changes; the address check does not.
SSH tunnels and local forwards
Local forward bind failures often trace to the same rule: the -L local address must be local. IPv6-only listeners ([::1], link-local scopes) need matching ip -6 addr output. Use ssh -vvv when the error does not name the address clearly.
Fix Docker and Podman "bind: cannot assign requested address"
Container publish syntax binds on the host first. If the host IP in -p is not assigned, the runtime fails before the container starts.
On RHEL 10.2 with Podman, publishing to a non-local host IP produces:
Error: cannot listen on the TCP port: listen tcp4 192.168.1.100:18080: bind: cannot assign requested addressConfirm which addresses the host owns:
ip -br addrThen either publish on an IP that appears in that list or omit the host IP when any interface is acceptable:
podman run --rm -p 8080:80 quay.io/rockylinux/rockylinux:10The same rule applies to docker run -p 192.168.50.20:8080:80 when 192.168.50.20 is not on the host.
Address exists but the service fails during boot
A valid static IP in config can still fail if the daemon starts before the interface or address is usable. The symptom is errno 99 in early boot logs, followed by success after systemctl restart SERVICE once networking is up.
Inspect boot-time service logs:
journalctl -b -u nginxReplace nginx with your unit name. Compare the failure timestamp with ip -br addr at login. If the address appears only after NetworkManager or cloud-init finishes, fix startup ordering: make the unit depend on network readiness, use a drop-in After= / Wants= on network-online.target where appropriate, or move address configuration earlier. The exact unit names vary by distribution; the diagnostic pattern is the same.
Check for ephemeral port exhaustion on outbound connections
connect() can return EADDRNOTAVAIL when Linux must pick an automatic source port but none is free in the ephemeral range. This shows up under high outbound connection rates: reverse proxies, load generators, API clients, and database pools opening many short-lived TCP sockets.
Read the local ephemeral range:
sysctl net.ipv4.ip_local_port_rangeSample output:
net.ipv4.ip_local_port_range = 32768 60999The default range contains about 28,000 candidate local port numbers for automatic TCP and UDP source-port allocation. Actual connection capacity depends on the destination addresses and ports, socket reuse behavior, reserved ports, and sockets already occupying usable local ports.
Summarize socket pressure:
ss -sSample output:
Total: 695
TCP: 24 (estab 9, closed 5, orphaned 0, timewait 1)
Transport Total IP IPv6
RAW 2 0 2
UDP 14 10 4
TCP 19 14 5Count sockets in TIME-WAIT when you suspect churn:
ss -tan state time-wait | wc -lA high TIME-WAIT count indicates heavy connection churn, but the count alone does not prove ephemeral-port exhaustion. Correlate it with errno 99 failures, the configured ephemeral range, and connections to the affected destinations. Also inspect connections to the affected destination with ss -tan and the ss command filters you already use for port troubleshooting.
Fix ephemeral port exhaustion correctly
Start with application design, not sysctl:
- Reuse TCP connections instead of opening one socket per request.
- Enable HTTP keep-alive, connection pooling, or HTTP/2 where the protocol allows.
- Cap parallel outbound workers and queue work when backends are saturated.
- Review why sockets sit in
TIME-WAIT— high counts are often normal after bursts, but endless one-shot connects are not.
Only after connection behavior is sane, review kernel limits. Check reserved ports before widening the range:
sysctl net.ipv4.ip_local_reserved_portsSample output when nothing is reserved:
net.ipv4.ip_local_reserved_ports =Reserved ports shrink the pool available to automatic source-port selection. Expanding ip_local_port_range to 1024 65535 on every host is a blunt instrument; tune only with measurement and document the change. See sysctl config for high-performance servers for broader tuning context.
Bind to a non-local IP with ip_nonlocal_bind (advanced)
By default Linux rejects binds to addresses that are not local:
sysctl net.ipv4.ip_nonlocal_bindSample output:
net.ipv4.ip_nonlocal_bind = 0Setting net.ipv4.ip_nonlocal_bind = 1 allows processes to bind IPv4 sockets to non-local addresses. That is useful in deliberate HA, proxy, or load-balancer designs where a floating or anycast IP will become local — not as a shortcut for a wrong listen IP in Nginx or Docker publish config. The kernel documents that enabling it can break applications that assume only local addresses succeed.
Troubleshoot IPv4 and IPv6 bind errors
An IPv6 bind such as [::1]:PORT fails with errno 99 when the address family or scope does not match the host. List IPv6 addresses:
ip -6 addr show scope globalSample output:
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 fd17:625c:f037:2:a00:27ff:fe2d:cd83/64 scope global dynamic noprefixroute
valid_lft 86397sec preferred_lft 14397secSee which services listen on IPv6:
ss -lnt6Sample output:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 [::]:22 [::]:*When an application forces IPv6 but the environment is IPv4-only for that bind, align the config or use ssh -4 only for SSH cases where verbose logs show an IPv6 bind attempt — not as a blanket fix for every errno 99.
Identify bind() vs connect() with strace
When logs do not say which syscall failed, trace socket setup:
strace -f -e trace=bind,connect python3 -c "import socket; socket.socket().bind(('192.168.1.100', 8080))"Sample output:
bind(5, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("192.168.1.100")}, 16) = -1 EADDRNOTAVAIL (Cannot assign requested address)A bind line with EADDRNOTAVAIL points to local IP or interface configuration. A connect line with the same errno points toward ephemeral port exhaustion or source-address selection under load. Run the same filter against the real service binary or interpreter wrapper.
Common mistakes
| Mistake | Why it is wrong |
|---|---|
| Treating errno 99 as "port already in use" | Busy ports normally return EADDRINUSE (errno 98) |
| Disabling SELinux first | Denials are typically EACCES, not EADDRNOTAVAIL |
Raising somaxconn for a missing IP |
Listen backlog does not create a local address |
Setting ip_nonlocal_bind=1 to hide bad config |
Masks misconfiguration; use only for intentional designs |
| Widening ephemeral ports without fixing connection churn | Symptom may return under the same load |
Killing TIME_WAIT sockets |
Normal TCP teardown; not a substitute for pooling |
| Restarting the service repeatedly | Does not fix a wrong bind IP or exhausted port range |
References
- Linux
bind(2)manual — https://man7.org/linux/man-pages/man2/bind.2.html - Linux
connect(2)manual — https://man7.org/linux/man-pages/man2/connect.2.html - Linux
errno(3)—EADDRNOTAVAILandEADDRINUSEdefinitions - Kernel documentation —
Documentation/networking/ip-sysctl.rst(ip_local_port_range,ip_nonlocal_bind,ip_local_reserved_ports) - Nginx
listendirective — https://nginx.org/en/docs/http/ngx_http_core_module.html#listen
Summary
Cannot assign requested address is errno 99 (EADDRNOTAVAIL). For listening services, compare the configured IP against ip -br addr and correct Nginx, SSH, database, or Docker publish bindings to addresses the host actually owns. When the IP is valid but boot fails, inspect journalctl -b against interface readiness. For outbound connect() failures under load, measure ephemeral port use with sysctl net.ipv4.ip_local_port_range and ss before tuning the kernel. Use strace when you need to separate bind() from connect(), and reserve ip_nonlocal_bind for architectures that intentionally bind before an address is local.

