Skip to content

服务器时间漂移:为什么 SSH 登录失败、双因子验证码也报错

昨天还能正常登录的服务器,今天 SSH 突然被拒,双因子验证码连续报"验证失败",可没人改过任何配置。这类故障最常见的原因不在网络、不在密钥,而在最不起眼的一处——服务器时间不同步。时钟漂移(clock drift)是典型的"沉默故障":它不报错、不告警、进程照常跑,却能在同一时刻让所有依赖时间的认证机制集体失效。本文拆清认证为什么会依赖时间、漂移如何逐一搞垮 TOTP 与 SSH 证书,以及怎么定位和预防。

认证系统为什么会依赖"时间"

很多"自然而然就能登录"的机制,背后都藏着一个时间依赖,只是平时时间是对的,这层依赖从未显形。把依赖方式列出来,就能理解为什么时钟一错,它们会以各不相同的方式拒绝你:

  • TOTP 动态口令:验证码是对"当前时间整除 30 秒的计数"做哈希得到的,时间错了,算出来的码就对不上。
  • SSH 证书:CA 签发的证书带"有效期自/至"两个字段,时钟漂到窗口外,认证直接报"证书尚未生效"或"已过期"。
  • Kerberos 票据:票据有生命周期,客户端与 KDC 时钟差超过默认 5 分钟就拒绝。
  • TLS 证书:浏览器与服务端都用 notBefore / notAfter 判断证书是否在有效期内。

时间在这里不是辅助信息,而是认证输入的一部分。下面重点拆最常踩坑的两个:TOTP 和 SSH 证书。

TOTP:差几十秒,验证码就失效

TOTP(RFC 6238)的"共享密钥"设计决定了它无需网络就能验证,但这套设计的另一面是:它对时间敏感到秒级。出码算法是:

counter = floor(unix_time / 30)
digest  = HMAC_SHA1(secret, counter)
code    = dynamic_truncate(digest) % 1000000

服务端拿到你输入的 6 位码,用自己持有的密钥按同样算法算一遍,对得上才通过。手机和服务端各自算各自的,中间不通信,所以只要两边的时钟差超过一个时间窗口(30 秒),同一个密钥也会算出不同的码。

实现里通常会留 ±1 个窗口的容差来吸收轻微偏差,也就是大约 ±30 秒(有的放宽到 ±90 秒)。这听起来不窄,但真实世界的漂移很容易超过它:一台从没配过 NTP 的服务器,硬件时钟几个月就能偏出几分钟;虚拟机被暂停、快照回滚、在线迁移,时钟会瞬间跳走。于是出现一个经典现象——密码没错、密钥没错,验证码就是过不去,而且往往是"手机明明显示的是对的码"。

还要记住一点:漂移是相对的,错的可能是任何一端。手机为了省电关了"自动同步时间"、换新手机时没等 Authenticator 完成时间校准、装了离线时钟的旧设备,都会让手机那一端的码和服务端对不上。所以排查时两端都要看——服务端用 timedatectl,手机则检查它有没有把"自动确定时间"关掉。只要两端差过一个时间窗口,无论谁对谁错,结果都一样:验证失败。

判断是不是 TOTP 时钟问题的第一个信号:同一时间、同一账号,换台机器登录也报验证失败,且错误信息是"验证码无效"而非"密码错误"。这时别急着重置密钥、也别怀疑手机,先去看服务器时间。

SSH 证书:有效期窗口让时间变成硬门槛

如果团队用的是 CA 签发的 SSH 证书(ssh-keygen -s 那套),认证对时间的依赖会更"硬"。和传统公钥不同,证书在签发时就写死了有效期窗口:

Valid: from 2026-09-12T00:00:00 to 2026-09-13T00:00:00

服务端校验时,只要本地时间落在这个窗口之外,无论证书内容多合法,一律拒绝。于是会出现两种诡异的报错:服务器时钟偏慢,证书被判定"尚未生效";时钟偏快,刚签的证书被判"已过期"。运维的直觉反应是"是不是签发错了、是不是要重新签",但其实证书本身没问题,是校验方的钟错了。

同样的逻辑也适用于主机密钥证书(host certificate)。当校验集中在入口时影响还会放大:用 SSH 代理服务器 统一转发时,入口那台机器时钟一偏,会同时拒掉一批资产的证书校验。这提醒我们:排查 SSH 认证失败时,把"证书是否在有效期内"和"服务器时间是否正确"放在同一张清单上查。想看某张证书签死的时间窗口,ssh-keygen -L -f 证书.pub 输出的 Valid: 一行直接给出起止时间;把它和当前 date 一对比,就能一眼分辨出到底是证书真过期了,还是校验方这台机器的钟不对。SSH 认证的整体原理,可参考 /zh/blog/ssh-authentication;而 TOTP 双因子这层如何叠加,见 /zh/blog/ssh-mfa-two-factor。

不止登录:时间错了,审计和排障一起乱

时钟漂移最阴险的地方,是它带来的损害远不止"登录不进去"。

  • 审计时间戳失真。等保与内审要求审计记录能还原"谁在什么时间做了什么"。如果记录操作的那台机器时钟是错的,操作顺序会被打乱,甚至出现"先有回放、后有登录"这种自相矛盾的记录。堡垒机的会话审计与操作回放(会话审计)高度依赖准确的时间轴,时钟一偏,审计证据的可信度就打折。
  • 多机日志对不上。跨服务器排查一次故障,要把多台机器的日志按时间对齐;只要有一台漂了几分钟,因果链就拼不出来。
  • 事件顺序无法判断。安全事件里"谁先谁后"往往就是结论本身,时间戳错了,结论就站不住。

所以时间同步不是"锦上添花的整洁",而是审计和取证能用起来的前提。

时钟为什么会漂:常见成因

知道成因,才能对症。按出现频率排:

  1. 根本没跑 NTP。服务器开机就靠硬件时钟(RTC)走时,晶振有误差,一天偏几秒,累积几个月就偏出几分钟。这是最常见的根因。
  2. 虚拟机的暂停/快照/迁移。虚拟机被暂停、快照回滚、跨宿主机在线迁移后,时钟会瞬间跳走,且 vCPU 调度导致的计时不稳定会让漂移持续累积。
  3. chrony 与 ntpd 只跑了一台或没开机自启。很多环境里"NTP 服务器"是有的,但"每台服务器都同步"这步漏了;还有的装了却没设开机自启,重启后裸奔。
  4. 系统挂起/睡眠。笔记本或某些裸机睡眠唤醒后,墙钟直接跳过睡眠时长。

还有一种更隐蔽的偏移来源——闰秒。地球自转和原子时并不同步,UTC 会不定期插入闰秒来对齐两者。多数现代系统靠持续在线的 NTP 服务自动消化闰秒,但老旧的 ntpd 曾因闰秒处理不当引发过大规模故障(2012 年、2017 年都有公开案例)。对跑业务的机器,这件事的结论很简单:用持续在线的 chrony,别靠一次性对时。

这里顺带澄清一个常见混淆:时区(timezone)不等于时间timedatectl 里 timezone 设错,只会影响显示,不影响"当前时刻"本身;真正导致认证失败的是"绝对时刻"偏了,跟时区无关。很多人一看到时间不对就去改 TZ,方向就错了。

怎么定位:先量"差了多少",再判断影响

排查分两步:先确认时钟到底偏了多少、偏没偏,再判断这个偏差是否落在认证容差之外。

第一步看系统状态。timedatectl 是最快的一眼:

$ timedatectl
               Local time: 一 2026-09-12 09:03:11 CST
           Universal time: 一 2026-09-12 01:03:11 UTC
                 RTC time: 一 2026-09-12 01:02:48 UTC
                Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: no
              NTP service: inactive
          RTC in local TZ: no

System clock synchronized: noNTP service: inactive 直接坐实"没在同步"。装了 chrony 的环境再看 chronyc tracking,重点看 System time(当前偏移)和 Last offset

$ chronyc tracking
Reference ID    : 0A1B2C3D (192.168.1.1)
Stratum         : 3
System time     : 0.000003421 seconds fast of NTP time
Last offset     : +0.000018901 seconds
RMS offset      : 0.000012345 seconds

如果 System time 显示偏了几十秒甚至几分钟,而你的 TOTP 容差只有 ±30 秒,结论就清楚了:认证失败就是它造成的。

第二步对照容差。把症状和可能原因放进一张表,比对着查:

症状最可能的原因先看哪一项
TOTP 验证码报错,但密码正确服务端时钟偏出 ±30schronyc tracking 的 System time
SSH 证书报"尚未生效/已过期",但证书没过期校验方时钟偏慢/偏快timedatectl 的 synchronized
多台机器日志时间对不上各机同步状态不一致逐台查 NTP 状态
刚做完快照/迁移后集中出问题虚拟机时钟跳变宿主机与来宾时间同步配置

第三步是确认修复真正生效。把时钟对准后,chronyc tracking 里的 System time 会往零收敛,但登录可能还要再等一个窗口才稳定——因为验证码每 30 秒滚动一次,恰好在边界上时会出现"这一秒对、下一秒不对"的抖动。另外别用 date -s 手动改完就收工:手动改只是临时盖住问题,NTP 没起、开机没自启,重启后照样偏回去。看到 synchronized: yes、偏移持续收敛到毫秒级,才算真修好。

偏差一旦确认,修复方向就是让时钟回到 NTP 时间。这里有个容易被忽略的坑:直接把时间"拨正"(step)和让时钟"慢慢追"(slew)是两回事。chrony 对大偏移默认直接 step,小偏移用 slew 平滑追赶;如果硬把时间往回拨,可能让日志出现重复时间戳、让依赖单调时钟的进程(如定时器、数据库)行为异常。所以修时钟之前,先想清楚这台的业务对"时间跳变"敏不敏感,而不是图快直接 date -s

让时钟自己待在线上的三点

预防比每次排查省心得多,落到三件事:

  1. 每台都跑 chrony,并开机自启。不要只维护一台"NTP 服务器",同步这件事必须逐台生效。systemd 环境一次 systemctl enable --now chronyd 即可,虚拟机则优先跟宿主机同步。
  2. 理解 slew 与 step 的边界,别乱拨时间。让 chrony 按它自己的策略处理偏移,遇到需要回拨的场景先评估业务影响。
  3. 给偏移加告警。在监控里对 NTP 偏移量设阈值(比如超过 1 秒就告警),比等用户被锁在外面才发现要早得多。

这三件事里第一件最关键:绝大多数这类认证故障都能追溯到"NTP 没跑或没自启",先查这个,别一上来就调各种参数。

堡垒机这类"认证入口"对时间尤其敏感:它既要在登录环节校验 TOTP(OTP 二次认证),又要给会话审计写时间戳,时间一偏,用户被锁、审计失真会同时发生。开源堡垒机 Next Terminal 也是这套逻辑——把堡垒机那台机器的时间同步做扎实,登录验证与操作审计(会话审计)就能稳定可依赖;这套方法对同类产品同样适用。

最后落回一个能直接用的判断:遇到"配置没动、凭证没错、多系统一起失败"的认证故障,先查时间。错误信息里有"验证失败/尚未生效/已过期"字样,且 timedatectl 显示 synchronized: no 或偏移超出容差,那问题基本就在时钟上。把钟对准,比反复重设密钥、重签证书更接近根因。