An app slowly stops accepting connections. ss shows this — what is wrong?
A web app grows unresponsive over hours and finally rejects new connections. Restarting fixes it for a while. Diagnose from the socket state below; name the fault and where the bug lives.
$ ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
1240 CLOSE-WAIT
14 ESTAB
6 TIME-WAIT
1 LISTEN
$ ss -tanp state close-wait '( sport = :8080 )' | head -2
Recv-Q Send-Q Local Address:Port Peer Address:Port Process
1 0 10.0.3.7:8080 10.0.3.90:41022 users:(("app",pid=811,fd=193))
$ ls /proc/811/fd | wc -l
1207
What is happening, and where is the bug?
Sockets pile up in CLOSE_WAIT on the app's own :8080: the peer sent FIN but the app never called close() to send its own FIN back. That is an app bug — it leaks sockets and descriptors until it hits the fd limit and stops accepting. Many TIME_WAIT, by contrast, is normal on whoever closed first.
- ✗Confusing
CLOSE_WAIT(local app hasn't closed) withTIME_WAIT(normal) - ✗Treating it as kernel tuning instead of a missing
close()in the app - ✗Raising the fd
ulimitto mask a leak that keeps growing regardless
- →Why is a large
TIME_WAITcount usually fine on the side that closed first? - →How would you find the code path leaking sockets from the fd list?
Read the state counts first. A socket enters CLOSE_WAIT when the remote peer has sent its FIN (it is done) and the kernel is now waiting for the local application to call close() so it can send its own FIN. 1240 sockets stuck there means the app is accepting connections, the clients hang up, and the app never closes its end — the fingerprint of a socket / file-descriptor leak in your own code. The /proc/811/fd count of 1207 confirms it: descriptors climb until the process hits its ulimit -n and can no longer accept(), which is why it stops taking connections and a restart temporarily "fixes" it.
Do not confuse this with TIME_WAIT: TIME_WAIT appears on whichever side closes first and is a normal, self-clearing state (a 2·MSL wait). A large TIME_WAIT count is usually benign; a large CLOSE_WAIT count is almost always an application bug.
# Which process and how fast is it leaking?
$ watch -n5 "ls /proc/811/fd | wc -l" # count trends upward → leak
# The bug: a handler that returns without closing the connection/response.
# e.g. a missing defer resp.Body.Close() / try-with-resources / finally close().
The fix is in the application, not the kernel: ensure every accepted connection (and every client it opens downstream) is closed on all paths — defer/finally/with. Raising the fd limit only delays the crash.