Y2038 — the second millennium bug (June 2026 update)

On 19 January 2038, at 03:14:07 UTC, the signed 32-bit Unix timestamp overflows. That sounds like tomorrow's problem, but in 2026 it is already today's: contracts with 30-year terms, database schemas being frozen now, IoT devices that will never see another firmware update. Over the past twelve months I have run into Y2038 bugs in three different codebases — none in the Linux kernel, all in application code. What is actually still broken in 2026 and what you can check this week.

What is the bug, really?

The Unix timestamp counts the seconds since 1 January 1970, 00:00:00 UTC. In a lot of systems that counter is stored as a signed int32 — a 32-bit signed integer. Its maximum value is 2,147,483,647. That many seconds after the Unix epoch lands at 19 January 2038, 03:14:07 UTC. One second later, the sign bit flips and the value goes negative — systems interpret that as 13 December 1901, 20:45:52 UTC.

It sounds esoteric, but it isn't. Anything that does date or time arithmetic is potentially affected: banking software calculating remaining loan terms, logging systems sorting by timestamp, devices with soldered-in real-time clocks. Above all: database columns of type INT (instead of BIGINT) that were created somewhere between 2003 and 2018, because back then nobody was thinking about 2038.

Y2038 goes by various names online: Epochalypse, Y2K38, Unix Millennium Bug. Most of those names were coined before anyone had actually audited the problem seriously. Today, twelve years before the deadline, the picture is mixed: some layers are clean, others are a minefield. The interesting question is not 'will the kernel overflow' — it is 'which layer of my stack still has int32?'.

When Y2038 first hit me in production

I was working on a project for a Swiss private bank in 2019, managing mortgage contracts. The software computed an end date for each contract — standard term between 10 and 25 years. In February 2019 a colleague from operations called: a contract someone had entered with a 30-year term suddenly showed up in the reports with an end date of 13 December 1901. The database column was an INT, the backend framework was computing milliseconds, and the overflow turned +2,147,483,647 into -2,147,483,648.

What surprised me at the time: the bug was not in the kernel, not in the database server, not in the programming language. It was in us — we had picked the wrong column type and never revisited the migration defaults. Linux was 64-bit, MySQL could have used BIGINT, the language (Java) had long for timestamps. Yet it overflowed in the application layer because somewhere a conversion to 32-bit seconds was happening. That is why 'the kernel is 64-bit, so I am safe' is a deeply dangerous assumption.

Since then I have applied the same patch in two more codebases — once in a PHP CMS that was setting cookie expiry dates, once in a telematics application for fleet vehicles. Both codebases had hundreds of '(int) casts' on seconds, and the bug was hiding in every single one. If your code still exists in 2026, the bug is hiding in yours too.

Linux: where the kernel actually stands in 2026

The Linux kernel has had 64-bit time values on 32-bit architectures since version 5.6 (February 2020). The system calls were migrated in a long, tedious clean-up over two years — the patch set is known in kernel slang as 'y2038 conversion' and primarily touched time_t, struct timespec, struct timeval and around 50 other syscalls. On 64-bit architectures the problem was never present: time_t has been a 64-bit integer there since the early 2000s.

That means: if you are running a modern Linux distribution (Ubuntu 22.04 LTS, Debian 12, RHEL 9, Alpine 3.18 or newer) on a 64-bit processor, your kernel is clean. macOS and every iOS version since iPhone 5s are clean too. Windows in its 21st-century versions has its own 64-bit time format and was never affected. The BSDs (FreeBSD, OpenBSD, NetBSD) have 64-bit time as well.

Where things get tight: embedded Linux on 32-bit ARM without CONFIG_64BIT_TIME, router firmware from the 2008–2018 era, OpenWrt builds that are stuck on an old kernel version, and anything using glibc < 2.32. If you have an industrial PC with ARMv7 or MIPS that has been running unchanged since 2017, the risk is high. On the Raspberry Pi 1 (no 64-bit CPU), Y2038 is still an open issue depending on the kernel variant and glibc version.

Databases: where it actually gets expensive

The biggest undetected Y2038 bomb is not in the OS, it sits in database schemas. MySQL has a data type TIMESTAMP that is internally stored as 32-bit seconds since the epoch and overflows on 19 January 2038, exactly. DATETIME, by contrast, reaches up to the year 9999. Anyone who has written created_at TIMESTAMP somewhere in the past 20 years has built in a bug that won't surface until they have already retired.

PostgreSQL is clean: TIMESTAMP WITHOUT TIME ZONE and TIMESTAMPTZ internally use a 64-bit microsecond value relative to 1 January 2000 and reach up to 294,276 AD. SQLite stores dates as text or real and is technically clean, but depending on the library wrapper can still inherit the problem. Oracle uses its own format and is unaffected. SQL Server likewise. The nastiest trap today is MySQL/MariaDB TIMESTAMP — and it is everywhere.

Concretely: if you have a WordPress, Magento, Shopware, Drupal or Joomla install from the past 15 years, look at your schema. wp_users.user_registered is a DATETIME (clean), wp_posts.post_date_gmt too. But every third plugin author opted for TIMESTAMP because it saves 4 bytes instead of 8. For your private plugin table that means: ALTER TABLE … MODIFY column DATETIME is your homework before 2038. Better now — migrating a fully populated MySQL TIMESTAMP index on a 50-million-row table doesn't take five minutes.

PHP, Python, JavaScript: state of play in 2026

PHP's time() has always returned an integer — on 64-bit systems a 64-bit integer, clean until 292 billion years after 1970. But: mktime(), strtotime() and DateTime on 32-bit PHP (which still exists!) are affected. If you are on a shared host whose PHP build is pre-2018, check PHP_INT_SIZE. If the value is 4, you have a problem. If it is 8, the language itself is safe — but the layer above it might not be.

Python's time.time() returns a float, which formally kills Y2038 — doubles can hold much larger values. But: struct.pack("i", timestamp) with 'i' as a 32-bit signed integer is sprinkled all over old scripts that talk to binary protocols. SCADA protocols, Modbus wrappers, ancient Wago PLC drivers — all potential trip wires. My most recent hit was in a 12-year-old Python lib that serialised marine data in NMEA format.

JavaScript looks safe at first glance: Date.now() returns milliseconds since the epoch as a Number (i.e. a double), and the maximum exactly representable integer is 2^53. Practically no Y2038 problem in the language core. But: anyone computing timestamps as Math.floor(Date.now() / 1000) and storing them in an Int32Array or via DataView.setInt32() manually puts the bug back in. WebSocket frames, Protobuf serialisations, IndexedDB schemas all do exactly that. The language is clean, the application layer isn't automatically.

Embedded, IoT, industry: the real bomb

This is where Y2038 becomes a systemic problem, because devices outlive their manufacturers' willingness to update. A 2015 generation of photovoltaic inverters has a lifespan of 20–25 years. A PLC in a water treatment plant runs for 30 years without being touched. A point-of-sale system in a Swiss train-station pizzeria — I saw one in 2024 that hadn't been touched since 2008. If that thing is still running in 2038 (likely), Y2038 will go live on it.

Specific sectors at elevated risk: industrial automation (PLC, SCADA), building management (KNX, BACnet bridges), medical devices with long certification cycles (ventilators, MRI controllers), automotive ECUs from 2010–2018, avionics subsystems, GPS trackers with old u-blox modules. The vendor response is everywhere the same: silence until 2035, then panic.

My realistically-pessimistic take: somewhere between 2035 and 2037 the first major Y2038 story will hit the tech press — probably from an unexpected sector like smart meters or electronic door locks. The first bug bounties will be written. Insurers will start demanding Y2038 audits. Anyone who is starting a five-year migration in 2035 has lost. Anyone who builds an inventory in 2026 has a twelve-year buffer. Right now is a really good time to start.

Code audit: what you can check this week

Concrete steps that take 30 minutes and surface a surprising amount:

  • Grep your database schemas: SELECT * FROM information_schema.columns WHERE data_type = 'timestamp'; on MySQL/MariaDB. Every row in the response is a candidate for migration to DATETIME.
  • Grep the codebase for (int) casts: in PHP grep -rn '(int)\s*\$\?[a-zA-Z_]*time' ., in JavaScript look for | 0 and Math.floor combined with Date. Every match is suspect.
  • Hunt for 32-bit builds: php -i | grep "Architecture", file $(which python3), uname -m. If anywhere you see i386, i686 or armv7l and the system has to keep running, you plan a migration to 64-bit.
  • Check binary serialisations: scan Protocol Buffers (.proto files) for int32 fields with 'time' in the name. Review Avro/Thrift schemas for i32 timestamps. These fields are often frozen at wire-protocol level and hard to migrate.
  • Test against 19 Jan 2038: in a test environment, set the system date to 18 January 2038, run your test suite, watch how much turns red. On many systems things break before the actual date, because time differences are computed across the threshold.

If you want to manually check a Unix timestamp, our Unix Timestamp Converter turns any second count into a readable date — including the famous 2,147,483,647 that marks the end of the 32-bit era.

Y2038 is not Y2K — and that's the problem

Y2K was a two-year sprint: politics, the press, insurers and IT departments collectively threw an estimated $300–600 billion globally at it between 1997 and 1999. Afterwards it was dismissed as 'nothing happened', because thanks to all that prep work, indeed nothing did. The punchline is: without the prep, it would have been a disaster.

Y2038 carries the same systemic weight but without the media attention. There is no Y2038 industry association, no Y2038 congressional hearing, no late-night-TV explainer episode. That could be a blind spot that only becomes visible in 2034. Anyone who starts now sits with a steady hand at the audit. Anyone starting in 2034 sits with a bug-bounty program nobody wants to pay for any more. It's worth investing a few hours into an inventory — even if you end up finding everything is clean.

What's on the road to 2038?

A realistic roadmap, which is what I am running for CalcSI and a couple of customers: 2025–2027 inventory (which systems, which layers?), 2027–2030 planning and budgeting, 2030–2036 implementation and rollout, 2036–2038 buffer for the three or four problems nobody had on the radar. Generous — and necessary, because migrations on real-world data volumes simply take time.

The good news in 2026: the toolchain is ready. Glibc, Linux kernel, every modern database engine, every mainstream language is clean. The bad news: nobody feels responsible for the application layer. That is exactly where a single day with grep and a weekend of schema migration has an enormous payoff right now. Do it while it's still cheap.

Frequently asked questions

Is it enough to move everything to 64-bit CPUs?

No. A 64-bit CPU does not automatically mean 64-bit time. Java int is still 32-bit, even on an ARM64 CPU. A MySQL TIMESTAMP column is still 32-bit seconds, even when the server runs on x86_64. A .proto file with int32 stays 32-bit even after a compiler update. You have to look at the application, not the hardware.

What happens at 03:14:07 UTC on 19 Jan 2038 in a Linux system with an outdated kernel?

The kernel sees a negative time_t and gets confused. uptime returns nonsense. Cron jobs either stop firing or fire at strange times. Logs get written with date 1901. SSL certificates are flagged invalid because their notBefore time appears to be in the future. In the worst case the boot process fails. Realistically: a degraded, half-functional system nobody can repair without pulling power and resetting the hardware clock.

How do I test my system without actually time-travelling?

On Linux: sudo timedatectl set-ntp false and then sudo date -s "2038-01-18 23:00:00 UTC" inside a test VM. Caution: TLS connections will break, keep the system off the network. With Docker it is cleaner: --env FAKETIME="2038-01-18 23:00:00" using the libfaketime library — it intercepts time calls without touching the host. The latter is my standard workflow for Y2038 audits.

Is there a Y2038 for millisecond timestamps?

Yes, but with a different date. A 32-bit signed integer for milliseconds overflows already on 19 January 1970, 19:14:07 UTC — so the bug has been there for 56 years, if anyone actually implemented it that way. A 32-bit unsigned integer for seconds lasts until 2106. A 64-bit integer for milliseconds reaches 292,277,026,596. Practically: use 64-bit, done.

My WordPress runs on MySQL — am I affected?

WordPress core tables use DATETIME and are clean. Mandatory check: SHOW CREATE TABLE wp_users\G — if you see user_registered datetime, you are safe on the core side. Plugin tables are a different story: SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE DATA_TYPE = 'timestamp' AND TABLE_SCHEMA = DATABASE(); shows you every 32-bit trap in your DB. In my experience this typically surfaced WooCommerce logs, backup plugins and older SEO tools.

Should I migrate now or can I still wait?

Take inventory now. Migration can be staged, not skipped. Migrating a MySQL TIMESTAMP column on 100m rows takes 2–6 hours of downtime depending on hardware — you don't want to do that in panic mode. Embedded updates on hardware with firmware signing often need years for the rollout logistics. Anyone starting in 2030 still has plenty of buffer. Anyone starting in 2035 doesn't. The sweet spot is between 2026 and 2030.

Note: The statements in this article are researched to the best of my knowledge (as of June 2026). They are not a substitute for a formal Y2038 audit on regulated systems (finance, medical, avionics). If you operate critical infrastructure, consult a specialised auditor.

Comments