When a TCP server shows thousands of connections stuck in CLOSE-WAIT while the clients calling it show thousands in TIME-WAIT, what does each pile mean, and which one is a bug?
answer
- which side, which state
- CLOSE-WAIT waits for your code
- no protocol timer ends CLOSE-WAIT
- TIME-WAIT count = close rate × duration
basics
~20 sCLOSE-WAIT piles up where the peer has closed but the local application never calls close: almost always a connection leak. TIME-WAIT piles up on the active closer under high connection churn: normal, and it clears itself after 2×MSL.
solid answer
~50 sRFC 9293 defines `CLOSE-WAIT` as waiting for a termination request **from the local user**: the stack has acknowledged the peer's `FIN` and will not send its own until the application closes, and no protocol timer moves it on. A growing count that never drains means code paths that stop using a connection without closing it, such as a missed close on an error path, and each leaked socket holds a descriptor and buffers indefinitely. The client's mirror of each leaked connection is `FIN-WAIT-2`, not `TIME-WAIT`. The clients' `TIME-WAIT` entries come from connections that closed properly with the clients closing first: every active close waits 2×MSL, so the steady-state count is close rate times the TIME-WAIT duration. That is bounded and harmless in itself, and the protocol-level way to shrink it is to open fewer connections, not to abort them with `RST`.
go deeper
Remember the pairing: CLOSE-WAIT is on the side whose application has not closed yet, TIME-WAIT is on the side that closed first.
Explain from the state machine why CLOSE-WAIT has no timer and TIME-WAIT does, and which peer state mirrors a stuck CLOSE-WAIT.
Diagnose the leak from the CLOSE-WAIT pile, size the TIME-WAIT pile with close rate times duration, and reject aborts or aggressive shortening as fixes.
Decide where teardown cost should live in a system: which tier closes first, how much connection churn the design creates, and which counts are alerted on as leaks versus tracked as load.
## Read the state, then the side A connection's teardown state says who has closed and who has not. Two facts from RFC 9293, the current TCP specification, settle this question: - `CLOSE-WAIT` "represents waiting for a connection termination request **from the local user**". The peer has sent `FIN`, the stack has acknowledged it, and the stack will not send its own `FIN` until the application closes. - `TIME-WAIT` is held by the endpoint that **closed actively**, the one whose `FIN` went first, and it MUST last 2×MSL before the connection is deleted. So the two piles in the question are on different sides for different reasons, and only one of them is a defect. ## CLOSE-WAIT: the stack is waiting for the application RFC 9293 has no timer that ends `CLOSE-WAIT`. The user timeout applies to data that has not been acknowledged, and keep-alive probing is optional. A connection in `CLOSE-WAIT` leaves it when the application closes it (moving to `LAST-ACK`), when the application aborts it, or when an error such as an incoming `RST` ends it. If the application has lost track of it, it can stay until the process exits. Each one holds a file descriptor, a connection record and its buffers. A count that climbs steadily and never drains is therefore almost always an **application bug**. Typical causes: 1. **A missing close on an error or timeout path.** The happy path closes; the exception path returns without doing so. 2. **A pool that forgets connections.** A connection pool removes a broken connection from its books but never closes the socket underneath it. 3. **Threads blocked elsewhere.** The code that would close the connection is waiting on a lock, a slow downstream call or a full queue, so it never reaches its close. 4. **Ignoring end of stream.** The reader never checks for a zero-byte read, so the peer's `FIN` goes unnoticed. To diagnose it, group the stuck connections by remote address and port, match them to the code that owns that kind of connection, and check whether the count follows error rates or traffic. Restarting the process empties the pile, because the operating system closes every descriptor on exit, but that hides the leak rather than fixing it. ## The mirror on the client: FIN-WAIT-2 Every server connection stuck in `CLOSE-WAIT` has a peer that sent the first `FIN`, got it acknowledged and is now in `FIN-WAIT-2`, waiting for a `FIN` that will not come. By the specification a connection can sit in `FIN-WAIT-2` indefinitely, because it is also the legitimate state of a half-closed connection. Stacks that time out a `FIN-WAIT-2` connection no application still owns are making an implementation choice; after that, the client has no record at all. So the clients' `TIME-WAIT` entries do **not** belong to the leaked connections. They belong to connections the server did close properly, where the clients had closed first. ## TIME-WAIT: arithmetic, not a leak Every active close parks one four-tuple in `TIME-WAIT` for its full duration. At steady state: `entries in TIME-WAIT ≈ active closes per second × TIME-WAIT duration` | Close rate | TIME-WAIT duration | Entries at steady state | |---|---|---| | 500 per second | 60 s (an implementation choice; Linux, for example) | 30,000 | | 500 per second | 240 s (RFC 9293: 2×MSL with MSL = 2 minutes) | 120,000 | | 5 per second | 60 s | 300 | The count tracks **connection churn**, not failure. A `TIME-WAIT` entry holds a small record and blocks reuse of that exact four-tuple; it holds no application resource. It becomes a problem mainly when one client opens connections to the same server address and port so fast that it runs short of four-tuples, which is a port-allocation question owned by the sockets topic. ## What actually reduces each pile | Pile | Where it lands | Real fix | What does not fix it | |---|---|---|---| | `CLOSE-WAIT` | the side that received the first `FIN` | close every connection on every code path | restarting, raising descriptor limits | | `FIN-WAIT-2` | the peer of a leaked `CLOSE-WAIT` | fix the leak on the other side | anything done on this side alone | | `TIME-WAIT` | the active closer | open fewer connections by reusing each for many exchanges; choose deliberately which side closes first | aborting with `RST`; shrinking the wait far below 2×MSL | ## Fixes that make things worse - **Aborting to skip TIME-WAIT.** An abort discards queued data, gives the peer a reset error instead of an orderly end, and throws away the protection TIME-WAIT exists to give against old duplicate segments. - **Moving TIME-WAIT instead of reducing it.** Making the server close first moves the entries to the server; that may be the right place, but the total is unchanged. - **Treating the TIME-WAIT count as the incident.** On the clients here it is the healthy half of the picture. The incident is the `CLOSE-WAIT` pile on the server.
- Why does a leaked TCP connection in CLOSE-WAIT not time out on its own?RFC 9293 defines `CLOSE-WAIT` as waiting for the local user to close, and gives it no timer. The user timeout covers unacknowledged data, which a quiet leaked connection has none of, and keep-alives are optional. So it ends only when the application closes or aborts it, when an error such as an incoming `RST` ends it, or when the process exits.
- Here the TCP server, not its clients, is the one piling up TIME-WAIT. What does that tell you?The server is the active closer: it sends the first `FIN`, for example when it closes connections after a response or after an idle period. That is not a bug; TIME-WAIT simply lands on whoever closes first. The count is still close rate times duration, and the protocol-level lever is fewer connections or a deliberate choice of which side closes.
- On the client of a leaked TCP connection, which state do you expect to find?`FIN-WAIT-2`. The client sent `FIN`, the server's stack acknowledged it, and the client now waits for a `FIN` the server's application never triggers. It can stay there indefinitely by the specification; some stacks time out a `FIN-WAIT-2` connection that no application still owns, which is an implementation choice.
saying these in an interview costs you the question
- CLOSE-WAIT is a timed state that clears itself if you just wait
- The side stuck in CLOSE-WAIT is the side that closed first
- TIME-WAIT on the clients means the server forgot to close connections
- Thousands of TIME-WAIT entries always indicate a connection leak
- Sending RST instead of FIN is a safe way to get rid of TIME-WAIT