In this article
Identify the process before trying to fix it
Open Activity Monitor and find the app named in your connection output. Check its process ID, or PID, rather than relying on an abbreviated command name. A browser, sync client, or development tool can have several helper processes.
Apple's process information guide explains how to inspect a process and its open files and ports. If you do not yet know which app owns the connection, start with our Mac app network-connections guide.
When the app is behaving normally, record the observation before changing anything. When it is stuck, save your work and try its normal Quit command before reopening it. Do not force-quit an unfamiliar system process merely because its name appears beside this state.
A successful restart can clear the immediate symptom without identifying its cause. Record the version and what you were doing first, so a recurring problem remains diagnosable.
Use lsof to inspect one PID
In Terminal, replace 12345 with the current PID from Activity Monitor:
/usr/sbin/lsof -nP -a -p 12345 -iTCP -sTCP:CLOSE_WAIT
This command reads socket information. It does not terminate a process, close a connection, or alter network settings.
The options have distinct jobs: -nP keeps addresses and ports numeric; -p selects the PID; -iTCP selects TCP sockets; and -sTCP:CLOSE_WAIT restricts the state. Crucially, -a combines the selections, so the output is for that PID and the requested sockets. The lsof manual documents these selectors.
Look at the PID, file descriptor under FD, and the pair of endpoints under NAME. Repeat the same command after the activity that seemed to trigger the problem. A PID can change when an app restarts, so recheck it before comparing samples.
No output means this query found no matching visible sockets at that moment. It is not proof that the app never used the network. Short-lived states, a changed PID, and visibility restrictions can all limit an observation.
What the state says—and what it does not
TCP allows each direction of a connection to finish separately. The peer sends a FIN when it has finished sending. The receiving side can then be in CLOSE_WAIT until its local application closes that side.
The state therefore points to a stage of connection shutdown. It does not identify the app's purpose, reveal the content sent earlier, or classify the connection as malicious. A short appearance during normal cleanup can be expected. A growing collection that persists while the app stalls deserves closer investigation.
Do not confuse it with TIME_WAIT. That state serves TCP's final shutdown and delayed-segment handling; it is not the same local-application wait. The authoritative definitions and transitions are in RFC 9293, section 3.3.2.
Likewise, LISTEN is a different state. It describes a socket waiting for incoming connections. Our Mac listener-address guide covers that case separately.
A controlled Mac test: EOF did not close the socket
On October 7, 2026, we created two TCP endpoints inside one disposable Python process on macOS 15.7.5, Apple Silicon. Both used 127.0.0.1, so the connection stayed on the Mac. No external server or installed application was tested.
After establishing the connection, we shut down the sending direction of one endpoint. We inspected the other endpoint with the exact lsof filters above, read its end-of-stream result, and finally closed its socket.
| Stage | Observed receiving endpoint |
|---|---|
| Both endpoints connected | ESTABLISHED; no row in the CLOSE_WAIT-only query |
| Peer stopped sending | CLOSE_WAIT; one matching row |
| Receiver read EOF but kept the socket open | Still CLOSE_WAIT; the same descriptor remained |
| Receiver closed its descriptor | That descriptor disappeared; no matching CLOSE_WAIT row |
The distinction was visible before and after reading EOF. Receiving the end-of-stream indication did not, by itself, close the application's socket. All test sockets were closed afterward.
This demonstrates the mechanism under controlled conditions. It does not establish that a particular commercial app has a leak, how long its cleanup should take, or whether its stalled operation has the same cause.
Decide from repeated evidence, not a single count
There is no universal number of CLOSE_WAIT rows that diagnoses a broken Mac app. A useful investigation connects socket observations to a repeatable action and an actual symptom.
| What you observe | Useful next step |
|---|---|
| A row appears briefly and disappears | Note it; avoid changing settings for that observation alone |
| Similar rows persist, but the app works | Compare later samples and record the activity involved |
| Rows keep accumulating and the app stops connecting | Save evidence, quit normally if possible, and check for an app update |
| An unfamiliar helper owns the rows | Identify its parent application before stopping anything |
For a support report, collect the app version, macOS version, PID, timestamps, a small relevant output sample, and the steps that reproduce the symptom. State whether restarting the app clears the rows and whether they return. Redact private endpoint names or addresses before sharing logs publicly.
Avoid treating kernel tuning, raised file-descriptor limits, or repeated forced termination as a diagnosis. Those changes can obscure the original pattern. For an app you develop, the next investigation belongs in its socket or connection-pool lifecycle: correlate the observed descriptor with the code responsible for releasing it.
Finally, keep transport state separate from privacy claims. Even an endpoint using port 443 does not show the contents of an exchange or verify its encryption; see what port 443 can tell you. The strongest conclusion from CLOSE_WAIT remains narrow: the peer finished its sending direction, and the local side has not completed closure.


