The macOS TCP Bug That Detonates at 49 Days
A uint32 cast in macOS's TCP clock code means any Mac left running past 49 days hits a networking wall. Here's exactly how it breaks—and why it matters.
Written by AI. Dev Kapoor

Photo: AI. Tomoko Hayashi
There's a category of bugs where, once you understand the mechanics, the horror isn't that it happened—it's that it was always going to happen. The number 49 days, 17 hours, 2 minutes, and 47 seconds belongs to that category. If you've been around systems programming long enough, that number should make your stomach drop. If it doesn't yet, give it a few minutes.
The team at Photon—a company running a fleet of Macs to monitor the iMessage service—noticed something consistent and deeply strange: their machines kept dying at almost exactly the same uptime. Not crashing dramatically. Just... quietly running out of the ability to make new TCP connections, memory climbing out of control, until the system was effectively bricked for network purposes. Every machine. Same timestamp. Every time.
That's not a fluke. That's a fuse.
The Setup: How TCP Ports Actually Work
To understand what's breaking, you need a quick detour into TCP connection basics—specifically, the concept of TIME_WAIT.
When your machine makes an outbound TCP connection, it uses one of roughly 32,000 available ephemeral ports on your side of the link. When that connection closes, the port doesn't immediately go back into the available pool. It sits in a TIME_WAIT state for about 30 seconds. The reason is sensible: the internet is messy, packets wander, and a stray packet from a recently-closed connection arriving at a port you've already reused could corrupt the new connection entirely. So you wait. 30 seconds, port goes free, life goes on.
This is all governed by a clock—specifically, a function in macOS's XNU kernel called calculate_tcp_clock. That function tracks elapsed milliseconds since boot and uses the value to determine whether TIME_WAIT has expired and a port can be recycled.
That function is where the bomb is buried.
The Bug: Four Bytes of False Economy
The PrimeTime broke down the actual XNU source code (Apple does publish the kernel—not under anything resembling MIT, but it's there) and found the culprit: the elapsed-seconds value gets multiplied by 1,000 to convert to milliseconds, then cast into a uint32.
A uint32 holds about 4.294 billion values. Four-point-two billion milliseconds is approximately—you already know where this is going—49 days, 17 hours, 2 minutes, and 47 seconds.
Once the machine's uptime crosses that threshold, the millisecond counter overflows and rolls back to zero. The TCP clock suddenly looks like the machine just booted. As the video explains it: "current now just wrapped all the way around. It's some small value, like 5,000, 1,000, 500, who knows what it is. Either way, it's a small value, therefore this statement will never execute, therefore the time internally in all the TCP stack will never move forward."
The consequence cascades quickly. If the TCP clock doesn't advance, TIME_WAIT can never expire. If TIME_WAIT can never expire, ports can never be recycled. Once you've burned through the available ephemeral port space—around 32,000 connections—you simply cannot open new TCP connections. Existing connections survive fine. Your browser tabs keep working. But try to load anything new and the machine stares at you blankly.
Photon confirmed this by running a controlled experiment: they hammered a Mac with connections starting five minutes before the rollover threshold. They watched the active connection count climb to around 200, sit at equilibrium as ports cycled in and out—and then, right as the clock overflowed, watch new connections stop being accepted entirely. The ports were frozen in TIME_WAIT permanently.
The Familiar Shape of This Problem
What makes this particular bug interesting beyond the "wow, huh" factor is its pedigree. This isn't a novel failure mode. It's the same shape as Y2K38.
Y2K38 is the Unix timestamp problem: time represented as seconds since January 1, 1970, stored in a signed 32-bit integer, which maxes out at 2.147 billion seconds—landing in January 2038. After that, the counter rolls negative, and anything calculating time relative to that epoch starts getting very confused about whether events happened before or after the Unix epoch.
The macOS TCP bug is structurally identical. Someone, somewhere, decided that a uint32 was sufficient for storing milliseconds-since-boot. At the time, maybe it was. A 32-bit value saving 4 bytes over a 64-bit one is the kind of micro-optimization that made sense when RAM was measured in kilobytes. Today, it's just a fuse with a known detonation point.
As the PrimeTime puts it: "You wanted to save four bytes of data instead of going to a 64-bit number. Causing problems for everybody."
That's a little uncharitable—the original code predates a world where machines were expected to run continuously for months—but the critique lands. The fix here is genuinely trivial: widen the type. A uint64 millisecond counter won't overflow for approximately 584 million years, which should cover most production workloads.
Who Actually Hits This?
The honest answer: probably not most Mac users. The average consumer machine gets rebooted for software updates, restarts after a kernel panic, or just gets closed and put in a bag. The 49-day threshold is high enough that casual use never approaches it.
But Photon's use case is instructive about who does hit it: anyone running Macs as headless infrastructure. Monitoring systems. CI runners. Build servers. iMessage relay bots. Any environment where someone thought "Macs are reliable, let's just leave them on"—which is a not-unreasonable thought—and then proceeded to do exactly that.
For those operators, this is a real operational problem with a specific, reproducible failure signature. The troubling part isn't the bug itself; it's that it could easily be mistaken for something else. Memory leak? Network misconfiguration? Hardware fault? Without knowing about the 49-day clock rollover, you'd be chasing ghosts. The Photon team deserves real credit for doing the forensic work to trace this all the way back to a specific cast in a specific kernel function.
The Open Source Footnote Worth Noting
There's a small irony in this story that I can't entirely ignore: the only reason Photon could diagnose this at the kernel level is because Apple publishes XNU. The license is, as the PrimeTime observed with visible discomfort while scrolling through it, emphatically not MIT—Apple's APSL has terms that constrain how you can use and modify the code. But the source is there, which meant a team with a mysterious fleet failure could go read the actual TCP clock implementation and find the actual bug.
That's the floor that open source sets, even when it's "open source with asterisks." Closed-source kernel = Photon files a bug report, waits, maybe gets a fix in a year. Open kernel = Photon reads the code, identifies tcp_subr.c, finds calculate_tcp_clock, and writes a blog post that explains exactly what's wrong and why.
Proprietary kernels have their own TCP bugs. We just don't always get to know what they are.
What Photon Found, and What Happens Next
The Photon blog post (photon.codes/blog/we-found-a-ticking-time-bomb-in-macos-tcp-networking) goes considerably deeper than the video summary—it includes the actual graphs from their connection experiments, the XNU source with the specific line identified, and the logic chain from overflow to port exhaustion. If you're running Macs as long-lived servers, it's worth your time.
The outstanding question, which neither the video nor the blog post has an answer to yet, is Apple's response. Has this been filed? Is there a fix in a beta? The bug is deterministic, reproducible, and traceable to a single type cast—the fix isn't complicated. Whether it gets prioritized is a different conversation entirely, and one that probably depends on how many people are actually running Macs past the 49-day mark in contexts where it matters.
The Heisenbugs—the ones that vanish when you look at them—are maddening to debug. But there's something almost worse about a bug that doesn't hide at all. It just sits there, counting down, waiting for you to leave a machine on long enough.
By Dev Kapoor, Open Source & Developer Communities Correspondent
We Watch Tech YouTube So You Don't Have To
Get the week's best tech insights, summarized and delivered to your inbox. No fluff, no spam.
More Like This
How Cloudflare Uses Lava Lamps to Encrypt the Internet
Cloudflare's San Francisco office has a wall of 100 lava lamps generating entropy for SSL/TLS encryption. Here's why computers can't be truly random.
Building a Nanosecond Clock Revealed a Hidden Time Bug
Jeff Geerling built a PTP clock showing time to the nanosecond, only to discover his network time server was drifting. A deep dive into precision timing.
Dozzle: The Docker Log Viewer That Does Less (On Purpose)
Dozzle is a 7MB tool that streams Docker logs to your browser. No storage, no database, no complexity. Better Stack shows why that's the point.
Unveiling Agent Skills in VS Code: A New Era in Workflow
Explore how Agent Skills in VS Code enhance productivity by enabling tailored workflows and automation.
The Security Hole We Keep Ignoring: Third-Party Scripts
After 50 years covering tech, I've seen this pattern before: developers linking to code they don't control, creating vulnerabilities that shouldn't exist.
The Fall of Stack Overflow: A Cautionary Tale
Exploring Stack Overflow's decline and the lessons from its community dynamics.
Anthropic's Opus 4.7: When Safety Guardrails Lobotomize the Model
Anthropic's Opus 4.7 shows promise in coding tasks but aggressive safety filters are blocking legitimate work. Is the tooling worse than the model?
Matt Wolfe's YouTube Playbook: Money, AI & Workflow
Matt Wolfe opens the books on his YouTube AdSense, AI video workflow, and why he thinks faceless AI channels are mostly a losing bet.
RAG·vector embedding
2026-05-07This article is indexed as a 1536-dimensional vector for semantic retrieval. Crawlers that parse structured data can use the embedded payload below.