In C, and in the operating systems written in it, time is stored in a type called time_t. For decades, 32-bit Unix systems were built using a signed 32-bit integer for time_t.2 It can count to 2,147,483,647 (231 − 1) and no further. Even today, on 32-bit Linux systems using the standard GNU C library, programs get a 32-bit time_t unless they're built with _TIME_BITS=64.3
Counting one per second from 1970, that ceiling arrives at 03:14:07 UTC on January 19, 2038. One second later the count doesn't reach 2,147,483,648. It wraps to the most negative value the type can hold, and every program reading it believes it's Friday, December 13, 1901.
Try it in your browser console:
new Date((2**31 - 1) * 1000).toISOString() // '2038-01-19T03:14:07.000Z'
new Date(-(2**31) * 1000).toISOString() // '1901-12-13T20:45:52.000Z'
2**63 / 31556952 // about 292 billion years
Picture a car odometer rolling over from 99999 to 00000, except this one rolls over to a negative number, and the software reading it can't tell anything went wrong. Timeouts land in the past, which is exactly what froze AOLserver in 2006.4 Any duration measured across the boundary comes out wrong by about 136 years.
The fix is known: make time_t 64 bits wide.2 A signed 64-bit count of seconds lasts about 292 billion years. The hard part is finding every place the 32-bit assumption hides, and reaching the devices that can't be updated.