时间跳跃导致证书验证/日志混乱的 chrony + hwclock 修复

10次阅读

根本原因是系统时间跳变导致TLS证书校验失败,需渐进校时、同步RTC、重启依赖服务并用游标查日志;chronyc tracking正常但报错时应检查内核时钟状态和进程时间缓存。

时间跳跃导致证书验证/日志混乱的 chrony + hwclock 修复

chrony 跳跃时间后证书报错:TLS handshake failed: certificate has expired

系统时间被大幅回拨(比如从 2025 年跳回 2024 年)后,chronyd 默认会拒绝同步,但若强制跳变(如用 chronyc makestep 或直接 date -s),会导致 Opensslcurljava 等依赖系统时钟的组件误判证书有效期——哪怕证书实际未过期,也会报 certificate has expirednot valid yet

根本原因不是证书真坏了,而是 TLS 握手时的 time(NULL) 返回了错误时间,校验逻辑直接失败。此时重启服务不一定管用,因为进程可能已缓存了错误时间戳。

  • 最稳妥做法:先让系统时间「渐进式」回到正确值,避免跳变 —— 确保 makestep/etc/chrony.conf 中启用且阈值合理,例如:makestep 1 -1(对任意偏移都允许步进,仅限调试;生产环境建议 makestep 0.128 3
  • 若已跳变,必须重启所有依赖证书的服务(systemctl restart nginx dockerd java-app),不能只 reload
  • 验证是否恢复:用 openssl x509 -in cert.pem -text -noout | grep -E "(Not Before|Not After)" 查证书时间,再用 date -R 对比系统时间是否落在区间内

hwclock 与 chrony 冲突导致开机时间反复错乱

常见于虚拟机或 Bios 时间不准的物理机:chronyd 启动时读取硬件时钟(RTC),若 RTC 本身严重偏差(比如停机数月后 RTC 慢了 3 天),而 chronyd 又没配置写回 RTC,就会出现「每次重启都倒退 N 小时」的现象,日志里 chronydSystem clock wrong by ... seconds,但始终无法收敛。

  • 确认 RTC 当前值:hwclock --show,对比 date,差值 > 60 秒就需干预
  • 让 chrony 掌控 RTC:在 /etc/chrony.conf 加两行:rtcsync(内核自动同步 RTC)+ makestep 1 3(启动 3 秒内允许跳变 ≤1 秒)
  • 禁用 systemd-timesyncd 和 ntpd:它们和 chrony 共存会争抢 RTC,运行 systemctl stop systemd-timesyncd && systemctl disable systemd-timesyncd
  • 首次修复后手动同步一次 RTC:chronyc makestep && hwclock --systohc(注意顺序:先让 chrony 校准系统时间,再写回 RTC)

日志时间戳错乱:journalctl 显示「昨天」的日志突然出现在今天

当系统时间跳变后,journald 不会重写已有日志的时间戳,但新日志会按当前(错误)时间写入,导致 journalctl --since "1 hour ago" 拿不到真实最近的日志,甚至查出大量「未来时间」条目(如 2025-04-01 03:00:00 出现在 2024 年机器上)。

  • 不要用 --since/--until 靠时间过滤,改用游标(cursor)定位:journalctl -o json | jq -r 'select(.__REALTIME_TIMESTAMP | tonumber > 1712000000000000) | .MESSAGE'(单位是微秒,可用 date +%s%N | cut -b1-13 换算)
  • 临时清空错乱日志(谨慎):journalctl --vacuum-time=1s(只保留最近 1 秒日志,适用于刚跳变完、日志量极少的场景)
  • 长期规避:在 /etc/systemd/journald.conf 中设 Storage=persistent + MaxRetentionSec=3month,并确保 RuntimeMaxUse 不过小,避免因磁盘满触发自动清理导致时间线断裂

chronyc tracking 显示 offset 正常但证书仍报错

执行 chronyc tracking 看到 Offset 是几毫秒,Leap statusNormal,但服务依旧连不上 https 接口,说明问题不在 chrony 同步精度,而在时间状态未被内核或用户态进程感知。

  • 检查内核是否启用了 CLOCK_REALTIME 调整:cat /proc/sys/kernel/panic_on_oops 无关,真正要看的是 adjtimex -p | grep -E "(status|tick)",status 值为 0x2000 表示时钟稳定;若为 0x1000(INS)或 0x4000(DEL),说明仍在插值调整中,部分应用可能读到中间态
  • 强制刷新 glibc 时间缓存(linux 5.10+):sudo ldconfig -p | grep libc 确认版本后,对关键服务加 LD_PRELOAD=/lib/x86_64-linux-gnu/librt.so.1 并重启,绕过旧版 glibc 的 time() 缓存缺陷
  • 最简验证法:在报错服务里插入一行代码打印 time(NULL)(C)或 Instant.now()(Java),确认返回值是否真实反映当前 UTC 时间 —— 这比看 chrony 状态更直接

时间跳变后的修复不是单点操作,chrony 配置、RTC 同步策略、服务生命周期、日志存储机制这四层必须同时对齐,漏掉任何一层都可能让问题在几天后复发。

text=ZqhQzanResources