the macos 49-day tcp bug is fixed, actually
Back in April, Photon found a bug in macOS where, once your Mac has been up for 2^32 milliseconds (49 days, 17 hours, 2 minutes and 47 seconds, if you want to set an alarm), it just stops opening new TCP connections. Ping still works, the connections you already have keep going, but anything new hangs, which is a wonderfully confusing thing to debug. A few weeks ago it took down our iOS CI runners at work, so I wrote it down in my agents’ memory, and from then on every agent that happened to run uptime for some unrelated reason would helpfully remind me that my Mac was about to die.
tl;dr is: the bug is fixed, so there’s no need to reboot your computer every ~49 days. Read on for more background.
Yesterday my main Mac got to 49.7 days, and instead of rebooting I figured I’d let it cross the line and see what actually breaks. Claude wrote a little probe that opened a new connection to 1.1.1.1, a new connection to itself over loopback, pinged a connection it had held open since before the wrap, and did a DNS lookup and a ping, every 20 seconds, logging it all to a file that would survive the reboot I was expecting to need.
Nothing broke. Ten hours later, it was still happily connecting to things. So we went and read the XNU source, which Apple publishes on GitHub. In macOS 26, calculate_tcp_clock() in bsd/netinet/tcp_subr.c sets the TCP clock from uptime in milliseconds, but only lets it move forward like this:
if (tmp < current_tcp_now) {
os_atomic_cmpxchg(&tcp_now, tmp, current_tcp_now, relaxed);
That’s a plain unsigned compare, so the moment uptime wraps around to a tiny number, it’s never bigger than the old huge one again, and the clock just freezes. TIME_WAIT sockets never expire, the ephemeral ports run out one by one, and there’s your hang. (macOS 15 and earlier advanced the clock by adding an increment instead, which wraps around just fine, so they never had the bug.)
In macOS 26.4 that line became:
if (TSTMP_LT(tmp, current_tcp_now)) {
TSTMP_LT does the comparison with wraparound, and the comment next to it now says that tcp_now must wrap correctly from 0xFFFFFFFF to 0, which is about as close to a changelog entry as we’re going to get.
Apple’s distribution-macOS repo says which xnu each macOS release ships, so we checked all of them:
| macOS | xnu | |
|---|---|---|
| 26.0 | 12377.1.9 | buggy |
| 26.1 | 12377.41.6 | buggy |
| 26.2 | 12377.61.12 | buggy |
| 26.3 | 12377.81.4 | buggy |
| 26.4 | 12377.101.15 | fixed |
| 26.5 | 12377.121.6 | fixed |
So if you have an always-on Mac on 26.3 or older, update it, and if you’re on 26.4 or newer you can probably stop counting days.
comments