Too Many CLOSE_WAIT Connections on Linux? Troubleshooting Connection Leaks, File Descriptor Exhaustion, and Application Cleanup
Create Time:2026-08-31 14:52:54
浏览量
1030

Linux CLOSE_WAIT connections, file descriptor usage, and application cleanup workflow

A growing number of CLOSE_WAIT connections on a Linux server usually means the remote peer has closed its side of the connection, but the local application has not closed the corresponding socket. A small, short-lived group of CLOSE_WAIT sockets can be normal. If the count keeps rising and the server also reports Too many open files, failed connections, or request timeouts, treat it as a likely connection leak.

This problem is rarely fixed by tuning a TCP timeout. The investigation should focus on the process that owns the sockets, file descriptor usage, exception paths, connection pools, blocking calls, and the connection lifecycle between proxies and upstream services.

What Does CLOSE_WAIT Mean?

When the remote endpoint sends a FIN, the local TCP stack acknowledges it and places the connection in CLOSE_WAIT. The local application must then call close() so the kernel can send its own FIN and continue the normal shutdown sequence.

In practical terms, CLOSE_WAIT means the peer has already requested closure, but the local application has not finished closing the socket. Common causes include an exception path that skips cleanup, a response body that is never closed, a blocked thread, a stalled event loop, a broken connection pool, or a client library that fails to release resources.

  • TIME_WAIT usually appears on the side that actively closes the connection and protects later connections from delayed packets.

  • FIN_WAIT_2 means the local side initiated closure and is waiting for the peer's FIN.

  • CLOSE_WAIT means the peer sent FIN while the local application still owns an open socket.

Confirm That CLOSE_WAIT Is Actually Accumulating

Start with a summary of TCP socket states:

ss -s

List all sockets currently in CLOSE_WAIT:

ss -tan state close-wait

Count them without printing the header:

ss -Htan state close-wait | wc -l

Watch the count over time:

watch -n 2 'ss -Htan state close-wait | wc -l'

A single snapshot does not prove a leak. The stronger signal is a count that rises continuously, does not fall during low traffic, or remains concentrated on the same local port, remote endpoint, and process.

Group Connections by Port and Remote Endpoint

Group the sockets by local address and port:

ss -Htan state close-wait | awk '{print $4}' | sort | uniq -c | sort -nr | head

Then group them by remote address and port:

ss -Htan state close-wait | awk '{print $5}' | sort | uniq -c | sort -nr | head

Output columns can vary slightly between distributions and ss versions, and IPv6 addresses contain colons. Inspect a few raw lines before assuming fields four and five are the local and remote endpoints. Avoid splitting IPv6 addresses on every colon.

If most sockets involve port 80, 443, a database port, or an internal API port, you can usually narrow the problem to a web service, reverse proxy, database client, or application integration.

Find the Process That Owns the Sockets

With sufficient privileges, ask ss to display process information:

sudo ss -tanp state close-wait

You can also use lsof:

sudo lsof -nP -iTCP -sTCP:CLOSE_WAIT

Record the process name, PID, local port, and remote endpoint. If most connections belong to one PID, continue the investigation inside that process. In a container or Kubernetes environment, map the host PID to the correct container or Pod so a proxy process is not mistaken for the application that created the connection.

Check Whether File Descriptors Are Near Their Limit

Every socket consumes a file descriptor. After identifying the PID, count the descriptors currently open by the process:

pid=<PID>
ls -1 /proc/$pid/fd | wc -l

Check the process soft and hard limits:

cat /proc/$pid/limits | grep -i 'open files'

Inspect system-wide file handle allocation and the configured limit:

cat /proc/sys/fs/file-nr
sysctl fs.file-max

If the process FD count keeps rising toward Max open files, logs may show Too many open files, connection failures, or errors opening ordinary files. Raising ulimit -n may delay the outage, but it does not repair a socket leak. Unreleased connections will eventually consume the larger limit as well.

Common Application-Level Leak Sources

Exception paths skip cleanup

The normal path may call close(), while timeout, parsing failure, early return, or exception paths do not. Review every exit path and use structured cleanup mechanisms such as finally, defer, context managers, or managed resource objects.

HTTP or database connections are not returned to the pool

A connection pool does not guarantee correct cleanup. A response body that is not fully consumed or closed, an unclosed result set, an unfinished transaction, or a borrowed connection that is never returned can leave sockets attached to the process. Compare active, idle, and waiting connection counts with the pool configuration and recycling logs.

The business timeout does not stop the socket operation

Some applications return a timeout to the caller while the network operation continues to block in the background. Configure connect, read, write, and overall request timeouts deliberately, and confirm that cancellation reaches the underlying client library. A timeout message alone does not prove that the socket was released.

Threads, event loops, or workers are blocked

A thread dump, runtime profiler, or event-loop delay metric can show whether the code responsible for closing connections is no longer running. For native processes, strace can help during a controlled diagnostic window, but tracing can add overhead and expose request data. Limit it to the affected PID and keep the capture short.

Proxy and upstream keepalive settings do not match

NGINX, application servers, service meshes, and upstream clients can have different keepalive limits and timeout expectations. One side may close idle connections while another component continues to retain them. Compare keepalive timeout, maximum requests per connection, upstream idle timeout, and health-check behavior across the full request path.

Use Process Evidence to Locate the Leak

Once the affected PID is known, combine socket data with process data instead of immediately changing global kernel parameters:

sudo lsof -nP -p <PID> | wc -l
sudo ls -l /proc/<PID>/fd | head
sudo cat /proc/<PID>/status | grep -E 'Threads|FDSize'
sudo ss -tanp state close-wait

Correlate the growth with request rate, error logs, dependency timeouts, thread count, connection-pool metrics, and deployments. If the rise begins after a release or appears only when one dependency fails, that timing can expose a missing cleanup path.

How to Restore Service Safely

If FD usage is close to the limit and new connections are failing, restore capacity before completing the full code investigation:

  1. Remove one affected instance from the load balancer or stop sending it new traffic.

  2. Capture the socket list, PID, FD count, application logs, and runtime diagnostics.

  3. Restart or replace that instance so the operating system releases its descriptors.

  4. Verify health checks and error rates before returning it to service.

  5. Repeat the process gradually for other affected instances.

Restarting releases leaked sockets, but it does not prove the root cause is fixed. If CLOSE_WAIT begins growing at the same rate after the restart, the application still contains the leak.

Changes That Usually Do Not Fix CLOSE_WAIT

Reducing net.ipv4.tcp_fin_timeout

tcp_fin_timeout primarily controls how long an orphaned connection remains in FIN-WAIT-2. A CLOSE_WAIT socket is waiting for the local application to close it, so reducing this value does not call close() on the application's behalf.

Only increasing the file descriptor limit

A larger limit can be appropriate when legitimate concurrency requires it, but it should follow capacity planning. When a leak exists, a higher limit merely allows the process to accumulate more abandoned sockets before failing.

Forcibly deleting every connection

Bulk connection termination can interrupt healthy requests and still leaves the cleanup bug unchanged. During an incident, isolate the responsible process or instance and use controlled traffic draining and rolling recovery.

Verify the Fix

After deploying the application fix, observe at least one representative peak and low-traffic period:

watch -n 5 'ss -Htan state close-wait | wc -l'
  • The CLOSE_WAIT count should fluctuate within a reasonable range instead of increasing continuously.

  • The process FD count should be able to fall and generally follow concurrent workload.

  • Connection success rate, application errors, and latency should return to normal.

  • Pool active, idle, and waiting counts should match the intended configuration.

  • The issue should not return after an equivalent traffic period.

Trend alerts for CLOSE_WAIT, process FD utilization, and connection-pool waiters are often more useful than a single fixed threshold. Growth rate can reveal a leak before the process reaches its hard limit.

Frequently Asked Questions

How many CLOSE_WAIT connections are too many?

There is no universal safe number. Duration, growth trend, FD utilization, service errors, and concentration in one process are more meaningful than a single count.

Will CLOSE_WAIT connections disappear automatically?

They normally disappear after the local application closes the socket, or after the process exits and the kernel releases its resources. Waiting for the operating system does not replace fixing the application lifecycle.

Will restarting NGINX solve the problem?

If NGINX owns the sockets, a restart or graceful replacement may temporarily release them. If an upstream application owns them, restarting NGINX may have no effect. Confirm the PID with ss -p or lsof first.

Is CLOSE_WAIT the same as ephemeral port exhaustion?

No. CLOSE_WAIT sockets consume sockets and file descriptors, while ephemeral port exhaustion concerns the local port range used for outbound connections. FD usage, TCP states, and local port allocation should be measured separately.

Conclusion

When a Linux server accumulates CLOSE_WAIT connections, use a consistent sequence: confirm the trend, group sockets by endpoint, identify the owning PID, check file descriptors, and inspect exception handling, response cleanup, connection pools, timeouts, and blocked execution paths. A rolling restart may restore capacity during an incident, but the durable fix is for the application to close every socket correctly and for monitoring to confirm that socket and FD counts recover after load falls.

References

  1. RFC Editor, Transmission Control Protocol (TCP), RFC 9293
    https://www.rfc-editor.org/rfc/rfc9293

  2. Linux man-pages, ss(8)
    https://man7.org/linux/man-pages/man8/ss.8.html

  3. Linux Kernel Documentation, IP Sysctl
    https://docs.kernel.org/networking/ip-sysctl.html

  4. Linux Kernel Documentation, /proc filesystem
    https://docs.kernel.org/filesystems/proc.html

  5. NGINX Documentation
    https://nginx.org/en/docs/

Sources checked: August 31, 2026.