Track down what owns a busy port, decide whether to kill it or rebind, and stop TIME_WAIT from blocking your restart.
You start your service and it dies before it ever serves a request:
$ ./myserver
error: listen tcp 0.0.0.0:8080: bind: address already in use
Node calls this EADDRINUSE, Python raises OSError: [Errno 98], Go prints the line above. Same kernel error underneath: bind(2) returned EADDRINUSE.
A listening socket is a tuple: protocol, local address, local port. The kernel lets exactly one socket own a given host:port combination for a protocol at a time. When your process asks to bind() to 0.0.0.0:8080 and something already holds that tuple, the kernel refuses. That is the whole story. It is not a firewall, not DNS, not permissions (usually). Something is already listening, or a recently closed socket is still holding the port.
Two questions get you to a fix: what owns the port, and do you want that thing gone or do you want a different port.
ss is the modern tool. -l listening, -t TCP, -n numeric (skip name resolution), -p show the process:
$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=4821,fd=23))
There it is: PID 4821, an old node process still holding 8080. Run ss with sudo, otherwise the Process column comes back empty for sockets you do not own.
lsof gives the same answer with a different lens and is handy when you want the full command line and user:
$ sudo lsof -nP -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 4821 kiril 23u IPv4 512377 0t0 TCP *:8080 (LISTEN)
fuser is the quickest when you just want the PID (and optionally to nuke it):
$ sudo fuser 8080/tcp
8080/tcp: 4821
If the process is a stale copy of your own service, kill it:
$ sudo kill 4821 # SIGTERM, lets it clean up
$ sudo kill -9 4821 # SIGKILL, only if it ignores the first
fuser -k 8080/tcp does the lookup and the kill in one shot. Be deliberate here: on a shared box that PID might be someone else's database. Confirm the command name before you signal anything.
If the port legitimately belongs to another service, do not fight it. Change your own port instead. Most servers take a flag or an env var:
$ PORT=8081 ./myserver
$ ./myserver --listen 127.0.0.1:8081
This one confuses people. You stop your server, immediately restart it, and get address already in use even though ss shows nothing listening. What you will see instead is a socket in TIME_WAIT:
$ ss -tan | grep 8080
TIME-WAIT 0 0 10.0.1.5:8080 10.0.1.9:52344
When a TCP connection closes, the side that closed first keeps the socket in TIME_WAIT for up to twice the maximum segment lifetime (commonly ~60s on Linux). This is deliberate: it lets any straggling packets from the old connection die out so they can't be mistaken for a new one. During that window the kernel, by default, refuses to bind a fresh listening socket to that same local address and port.
The wrong fix is to wait, or to lower tcp_fin_timeout. The right fix lives in your code: set SO_REUSEADDR on the listening socket before you bind. It tells the kernel a TIME_WAIT socket on that address is fine to reuse.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # before bind()
s.bind(("0.0.0.0", 8080))
s.listen()
Most frameworks set this for you. Python's http.server with allow_reuse_address = True, Go's net.Listen, and Node's net all enable it by default, which is why you rarely hit TIME_WAIT in those. If you wrote the socket code by hand and skipped it, this is your bug.
A related option, SO_REUSEPORT, is different: it lets multiple sockets bind the same port at once so several worker processes can each accept connections, with the kernel load-balancing across them. Reach for it when you want that behavior, not as a TIME_WAIT workaround.
Address matters as much as port. 0.0.0.0 means "every interface on this host." 127.0.0.1 means loopback only. A socket bound to 0.0.0.0:8080 covers 127.0.0.1:8080 too, so a second process trying 127.0.0.1:8080 collides with it. The reverse can also surprise you: without SO_REUSEADDR, you generally cannot have one process on 127.0.0.1:8080 and another on 0.0.0.0:8080. When ss shows 0.0.0.0:8080 and you thought you only bound loopback, that wildcard bind is the thing in your way.
Binding a port under 1024 needs privilege. Try to run a web server on 80 as a normal user and you get:
bind: permission denied
That is EACCES, not EADDRINUSE, but it lands in the same "won't start on this port" bucket. Rather than run the whole service as root, grant just the bind capability to the binary:
$ sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver
$ getcap /usr/local/bin/myserver
/usr/local/bin/myserver cap_net_bind_service=ep
Now it can bind 80 or 443 while running as an unprivileged user. For interpreted apps, set the capability on the interpreter or, cleaner, listen high (8080) and let a reverse proxy or an iptables redirect handle 80.
Docker has its own flavor of this:
$ docker run -p 8080:80 nginx
docker: Error response from daemon: driver failed programming external
connectivity on endpoint: Bind for 0.0.0.0:8080 failed: port is already allocated.
Same root cause, one layer up: dockerd (or another container's published port) already holds the host side. The lookup is the same ss -ltnp on the host, plus docker ps to spot the container publishing 8080. It's common enough that it gets its own writeup: Docker port already allocated.
Run sudo ss -ltnp 'sport = :PORT' first, every time. It answers "what owns it" in one line. If the owner is a stale copy of your own service, kill it and restart. If it belongs to something real, move your service to another port instead of fighting for it. If ss shows nothing listening but you still can't bind, check for TIME_WAIT and fix it properly by setting SO_REUSEADDR in the code rather than sleeping through the timer. And when the port is below 1024, grant cap_net_bind_service instead of reaching for root. Ninety percent of these are a forgotten background process, and ss finds it in seconds.
For the broader toolkit, see our Linux troubleshooting guide.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
The IaC landscape fractured after the Terraform license change. This is the map to what each tool is actually best at, and how to choose without regret.
A practical method for reading free, top, and ps correctly so you attribute Linux memory to a real cause instead of guessing.
Explore more articles in this category
A field-tested workflow for diagnosing why a systemd unit refuses to start, from status output to exit codes to the usual root causes.
When a Linux box misbehaves, the same dozen problems come up again and again. This is the map: what each symptom means and the fast path to the fix.
A likelihood-ordered checklist for tracing "Permission denied" on Linux through mode bits, ownership, ACLs, SELinux, and mount options.
Evergreen posts worth revisiting.