#1150·openresty

[Windows x64] Log timestamps fall back to 1970 after 2038 due to 32-bit `timeval.tv_sec` overflow

Author: HezhongqiCreated Sep 6, 2026Updated Sep 6, 2026

Summary

On Windows x64, OpenResty log timestamps fall back to a date around 1970 when the Windows system clock is set past 2038-01-19 03:14:07 UTC.

The tested build has a 64-bit time_t, but the Win32 implementation of ngx_gettimeofday() stores Unix seconds through a 32-bit long. At 2038-01-19 03:14:08 UTC, the conversion overflows to a negative value. ngx_gmtime() later clamps that negative value to zero, so the cached log time is formatted as the Unix Epoch.

Environment

  • OpenResty: 1.31.1.1
  • Bundled nginx core: 1.31.1
  • ngx_lua: 0.10.31rc5
  • OpenSSL: 3.1.5
  • Compiler: GCC 16.1.0, MSYS2 MinGW64
  • Build options: -O2, 64-bit target
  • Operating system: Windows 25H2 x64, build 26200.9168
  • sizeof(time_t): 8
  • sizeof(long): 4

Relevant nginx -V output:

nginx version: openresty/1.31.1.1
built by gcc 16.1.0 (Rev6, Built by MSYS2 project)
built with OpenSSL 3.1.5 30 Jan 2024

Minimal configuration

nginx
worker_processes  1;
error_log  logs/error.log notice;

events {
    worker_connections  64;
}

http {
    log_format y2038 '$time_iso8601 $msec "$request" $status';
    access_log logs/access.log y2038;

    server {
        listen 18080;

        location / {
            return 200 "ok\n";
        }
    }
}

Steps to reproduce

  1. Stop OpenResty.
  2. Set the Windows system clock to 2039-01-01 or another date after the 2038 boundary. Automatic time synchronization may need to be disabled temporarily.
  3. Start OpenResty with the configuration above.
  4. Run curl.exe http://127.0.0.1:18080/.
  5. Inspect logs/access.log and logs/error.log.
  6. Restore automatic time synchronization after the test.

Expected behavior

The log entries should contain the configured system date in 2039.

Actual behavior

The log timestamp falls back to a date around 1970. The exact displayed hour depends on the local time zone and log format, but the year is 1970 instead of 2039.

Root cause

The Win32 implementation obtains a correct 64-bit FILETIME, but converts the seconds to long when filling struct timeval:

c
intervals -= 116444736000000000;

tp->tv_sec = (long) (intervals / 10000000);
tp->tv_usec = (long) ((intervals % 10000000) / 10);

Windows uses the LLP64 data model, so long remains 32 bits in a 64-bit process. Although time_t is 64 bits in this build, both the cast and the Win32 struct timeval.tv_sec field truncate the Unix timestamp to 32 bits.

At the boundary:

2038-01-19 03:14:08 UTC = 2147483648 Unix seconds

the converted value becomes negative. ngx_gmtime() in src/core/ngx_times.c then normalizes it to zero:

c
if (t < 0) {
    t = 0;
}

The observed path is therefore:

GetSystemTimeAsFileTime()
    -> correct 64-bit FILETIME
    -> ngx_gettimeofday()
    -> conversion through 32-bit long
    -> negative Unix timestamp
    -> ngx_gmtime() clamps the value to zero
    -> the cached log time is formatted as a date in 1970

Suggested direction

Avoid storing Unix seconds through long in the Windows implementation. One source-level approach that has been validated locally is to use an nginx-owned timeval type whose seconds field is time_t:

c
typedef struct {
    time_t  tv_sec;
    long    tv_usec;
} ngx_timeval_t;

The Win32 conversion can then assign the seconds to time_t, while Unix keeps its existing ABI internally through an alias:

c
typedef struct timeval ngx_timeval_t;

This is only a proposed implementation direction. I am happy to adjust the internal API shape based on maintainer feedback, especially if compatibility with third-party modules requires a different approach.

Validation already performed

The source-level fix was tested with the following values:

  • Unix Epoch
  • 2038-01-19 03:14:07 UTC
  • 2038-01-19 03:14:08 UTC
  • A timestamp in 2039, including microseconds
  • 2106-02-07 06:28:16 UTC, corresponding to Unix timestamp 2^32

The following checks passed on the OpenResty 1.31.1.1 test build:

  • Full Windows x64 build
  • 8/8 runtime verification cases
  • Correct access and error log timestamps after the 2038 boundary
  • Offline clean rebuild
  • Source archive inventory check
  • Object-code inspection at -O2; the conversion helper was inlined

The implementation adds no system calls, locks, heap allocations, or extra time-update operations.

Upstream nginx tracking

The root cause is in nginx's Win32 platform layer and is tracked upstream:

This OpenResty issue records the downstream impact and can track when the nginx fix is accepted or included in a bundled nginx release.