One integer, one deadline

Unix time is a single number: seconds since 1 January 1970 UTC, historically stored in a signed 32-bit integer. The largest value that fits is 2,147,483,647, which is 03:14:07 UTC on 19 January 2038. Add one more second and the sign bit flips. You can watch the wrap happen in any language that has 32-bit integer arithmetic:

$ node -e "const t = (2147483647 + 1) | 0; console.log('time_t as int32:', t); console.log(new Date(t*1000).toUTCString())"
time_t as int32: -2147483648
Fri, 13 Dec 1901 20:45:52 GMT
node v22.22.3 · macos 26.6

Not an error, not an exception. A perfectly valid negative number that decodes to a Friday in 1901, which is exactly why this class of bug travels so far before anyone notices: every layer keeps happily computing with it. If you want to poke at the boundary values yourself, the Unix timestamp converter resolves 2147483647 and its neighbors in both directions.

It first failed in 2006, 32 years early

The earliest widely documented sighting is a lovely piece of computing history. AOLserver, the web server behind AOL, let admins configure a database timeout, and the docs suggested representing “never time out” as a huge value: one billion seconds. That worked for years, because now plus a billion seconds stayed inside the 32-bit range.

Until 01:27:28 UTC on 13 May 2006. From that second on, now plus one billion crossed 19 January 2038, wrapped negative, and every “never expire” deadline was suddenly a century in the past. Requests timed out instantly, and the fix was to configure a smaller number.

The mechanism generalizes: any code that adds an offset to the current time meets the boundary that offset early. Thirty-year mortgages met it in 2008. Twenty-year certificates met it in 2018. The deadline is 2038 minus your longest look-ahead, and for a lot of software that date is long gone.

Where it bites in 2026

The failures today cluster in three places:

  • Embedded and 32-bit devices with long lifetimes. Routers, meters, industrial controllers, car head units. A device shipped in 2020 on a 32-bit userland with a 20-year service life sails straight through 2038, and the vendors patching them now would rather have started earlier. Anything that validates a certificate or a license whose expiry lies past 2038 can fail today, because notAfter gets parsed into a time value on the device itself.
  • Database columns. MySQL and MariaDB TIMESTAMP ends at 2038-01-19 03:14:07 UTC. Inserting a 2039 date fails or clamps depending on version and SQL mode. Schedules, subscriptions and retention dates hit this from the writing side first.
  • File formats and protocols with 32-bit fields. Classic examples are old filesystem timestamps, zip archive extra fields and binary logs. The struct said 32 bits in 1998 and the files are still being produced.

The pattern to internalize: 2038 stopped being a future event the day software started handling future dates beyond it. That day was decades ago.

What is fixed, and what quietly is not

The good news is real. On 64-bit platforms time_t is 8 bytes and lasts about 292 billion years. The Linux kernel grew 64-bit time syscalls for 32-bit architectures in 5.6, glibc and musl can build 32-bit userlands with 64-bit time, and Debian rebuilt its 32-bit ports that way. A current stack has no excuse left.

The catch is that “can be built correctly” is not “was rebuilt”. Every already-flashed firmware, every statically linked 32-bit binary and every vendor SDK frozen in 2016 keeps its 32-bit time_t until someone recompiles it, and recompiling is exactly what does not happen to devices in the field.

And some of it is schema, not code:

TIMESTAMP, range ends 2038-01-19
CREATE TABLE contracts (
  id BIGINT PRIMARY KEY,
  expires_at TIMESTAMP
);
-- INSERT of '2039-06-01' fails or clamps
DATETIME, range ends 9999-12-31
CREATE TABLE contracts (
  id BIGINT PRIMARY KEY,
  expires_at DATETIME
);
-- store UTC explicitly, the column
-- no longer converts for you

The right-hand column has a cost: DATETIME does not do the automatic UTC conversion TIMESTAMP did, so the application must store UTC on purpose. Which it should be doing anyway, for the reasons laid out in how to store timestamps without regrets.

Checking your own stack

An afternoon of looking beats any amount of reading about it.

  • Grep schemas for TIMESTAMP columns that hold future dates. Expiries, renewals, schedules and retention deadlines are the ones that will cross the line first.
  • Insert a date in 2040 through your own API in staging and follow it all the way down. It should come back as 2040, not as 1970, 1901 or an error.
  • List what still runs 32-bit. file on the binary or getconf LONG_BIT on the box answers it quickly, and containers count too.
  • Check long-dated artifacts you issue: internal CA certificates, license files, signed tokens with far-future expiry claims. Whether a consumer parses them into 32 bits is decided on the consumer, which you do not always control.
  • For anything embedded you ship or operate, put a clock-forward test in the lab plan: boot the device with the clock set to February 2038 and see what breaks.

The question that travels well: everywhere a timestamp is stored, ask what width it is stored in. The seconds themselves are innocent. The container is the bug.

2038, asked concretely

Is the year 2038 problem fixed?

On mainstream 64-bit servers and current operating systems, yes. It is not fixed in 32-bit embedded devices, in database columns like MySQL TIMESTAMP, in file formats that store 32-bit timestamps, and in old binaries that nobody will recompile. The remaining problem is an inventory problem, not a kernel problem.

What exactly happens when 32-bit time overflows?

At 03:14:07 UTC on 19 January 2038 the counter reaches 2,147,483,647 and wraps negative, which decodes to 13 December 1901.

Does MySQL have a 2038 problem?

The TIMESTAMP column type does: its range ends at 2038-01-19 03:14:07 UTC, still true in MySQL 8. DATETIME reaches the year 9999 and is the usual replacement, at the price of losing the automatic UTC conversion TIMESTAMP performs. PostgreSQL is not affected, its timestamp type is 8 bytes.

Are 64-bit systems affected by 2038 at all?

The OS itself is not, a 64-bit time_t lasts around 292 billion years. A 64-bit system still breaks where a 32-bit width is baked into something it stores or speaks: a database column, a binary file format, a network protocol field, or an old 32-bit binary it still runs.

How do I test my application for year 2038?

Feed it dates beyond the boundary and watch what comes back. Insert 2039 expiry dates, run a report over a range ending in 2040, set a test system’s clock past 2038 in a VM. The two failure shapes to look for are dates that come back as 1901 or 1970, and comparisons that suddenly invert because a wrapped value is negative.

Why 2038 and not some other year?

A signed 32-bit integer holds 2,147,483,647 seconds, and counted from 1 January 1970 they run out on 19 January 2038.

What was the first real 2038 failure?

The commonly cited first sighting is from May 2006, in AOLserver. Its configuration used a timeout of one billion seconds to mean never, and on 13 May 2006 at 01:27:28 UTC, now plus a billion seconds crossed the 2038 boundary. The computed deadline wrapped into the past, every request timed out immediately, and the fix was to configure a smaller timeout. The bug arrived 32 years ahead of its own deadline.

Is there a year 2106 problem too?

Yes, for the systems that chose unsigned 32-bit seconds, which overflow on 7 February 2106. That bought time but dropped the ability to represent dates before 1970.