VPS时间漂移怎么查?修复NTP同步和2FA登录
VPS时钟漂移会导致TOTP验证码失效。本文教您用chronyc和timedatectl定位问题,检查UDP 123,并修复未同步或守护进程冲突。
VPS 时钟为何会漂移
VPS 时钟会漂移,是因为没有任何机制对其进行校准。内核根据硬件计数器计算时间,而硬件计数器的运行速度可能略快或略慢。如果没有运行时间同步客户端,这个微小误差会在每小时累积。
在虚拟机中,还有第二个原因:虚拟机与其他虚拟机共享物理 CPU,因此未被调度运行的时间无法计入自身时间。
在当前的 KVM 虚拟机中,计数器本身通常不是实际问题。半虚拟化 kvm-clock 源读取由主机维护的值,因此运行正常的虚拟机可以紧密跟随主机时钟。明显错误的时钟通常是由更常见的原因导致的:没有运行同步守护进程,或者同时运行了两个守护进程并相互冲突,又或者出站 UDP 端口 123 根本无法离开服务提供商的网络。虚拟机的时间校准依赖主机或 NTP(网络时间协议),而不是自身的振荡器。
错误的系统时钟实际会导致什么问题
- TOTP(基于时间的一次性密码)双因素验证码会无法匹配,因此即使服务器密码和密钥都正确,您仍会被锁定在服务器之外。
- 一分钟前签发的证书会被拒绝,并且
curl会输出SSL certificate problem: certificate is not yet valid。 apt update会因E: Release file for ... is not valid yet (invalid for another 1d 2h 3min 4s)拒绝仓库。- 定时任务会在错误的时间执行。时钟跳变还可能导致一个任务执行两次,而另一个任务被跳过。
- 两台服务器的日志无法对齐,因此只能依靠猜测还原事件时间线。
允许的误差范围比大多数人预期的更小。下面的数值是文档中记录的默认值,不是测试测量结果。
The data behind this chart
[
{
"label": "TLS certificate boundary",
"tolerance_seconds": 0,
"documented_in": "RFC 5280"
},
{
"label": "chrony steps instead of slewing",
"tolerance_seconds": 1,
"documented_in": "Debian and Ubuntu chrony.conf"
},
{
"label": "TOTP code, one time step",
"tolerance_seconds": 30,
"documented_in": "RFC 6238"
},
{
"label": "Kerberos clock skew",
"tolerance_seconds": 300,
"documented_in": "MIT krb5 default"
}
]TOTP 根据一个计数器计算验证码,该计数器每隔 30 秒递增一次。大多数验证器允许前后各偏移一个时间步长。前后各半分钟的误差就是全部容许范围。Kerberos 的容错范围大得多,默认允许的时钟偏差为 300 秒。证书完全不具备容错能力:系统会将其与固定时间点进行校验,并允许 0 秒的宽限期。因此,时钟提前一秒就会拒绝一张完全有效的证书。
三种时钟,以及真正重要的时钟
系统时钟才是真正重要的时钟。它是内核的 CLOCK_REALTIME:自 1970 年 1 月 1 日 UTC 起经过的秒数,保存在内存中,所有需要记录时间的组件都会读取它。日志行、证书检查、TOTP 代码和文件修改时间都来自系统时钟。有人说服务器时间不正确时,指的就是这个时钟。
硬件时钟也称 RTC(实时时钟),是一个在机器关机期间仍持续运行的独立计数器。在物理机上,它是由电池供电的芯片。在 guest 中,它由 hypervisor 模拟,因此主要反映 host 的状态。Linux 会在启动时读取一次硬件时钟,将其作为初始值,然后使用自己的计数器继续计时。timedatectl 会在 RTC time 行显示它。在 VPS 上不要根据这一行进行故障排查,因为它反映的是 host 对时间的判断,而不是系统时钟的同步状态。在容器中通常根本没有 /dev/rtc,因此 hwclock --show 会失败并显示 hwclock: Cannot access the Hardware Clock via any known method.
时钟源决定内核在两次读取之间使用什么进行计数。运行以下命令,查看内核选择的时钟源:
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
cat /sys/devices/system/clocksource/clocksource0/available_clocksource在 KVM 上通常会看到 kvm-clock。它读取由 host 维护的值,因此即使 KVM guest 完全没有 NTP 客户端,短时间内通常也能保持大致准确。tsc 是 CPU 自己的计数器。Xen guest 会报告 xen,Hyper-V guest 会报告 hyperv 时钟源。除非有测量结果证明需要更改,否则不要修改此设置,因为内核已经会根据硬件选择它信任的最佳时钟源。
有些 host 还会将 PTP(精确时间协议)设备提供给 guest,使 chrony 可以直接读取 host 时钟,而不必通过网络读取。建议检查该设备,但共享 VPS 通常无法使用它:
sudo modprobe ptp_kvm
ls /dev/ptp*
cat /sys/class/ptp/ptp0/clock_name如果 modprobe 失败,或者没有显示任何设备,说明 host 未提供该设备,此时应使用网络 NTP。如果 clock_name 显示的是 KVM 虚拟时钟,chrony 可以在配置中使用包含 refclock PHC /dev/ptp0 poll 2 的行。
在本机读取时间状态
先运行一个命令。它会在一个屏幕内回答“是否有程序在校准此时钟”。
timedatectl读取以下各行,不要只相信自己记住的某个数字:
Local time和Universal time表示同一时刻,分别使用本地时区和 UTC 显示。如果两者相同,说明本机已经使用 UTC。RTC time是前文所述的硬件时钟。在 VPS 上忽略它。Time zone表示系统用于格式化本地时间的设置。System clock synchronized是内核自身的标志。时间守护进程信任其时间源后会设置此标志,因此no表示自启动以来没有程序校准过此时钟。NTP service专门报告 systemd-timesyncd 的状态。在运行 chrony 的机器上,n/a属于正常情况,因为该机器未安装 timesyncd。System clock synchronized: yes与NTP service: n/a同时出现,表示 chrony 正在执行校时,并且内核也认可该状态。
接下来,检查时钟偏差有多大。不要拿手机上的时间进行目测比较。如果 chrony 正在运行:
chronyc tracking
chronyc sources -vchronyc tracking 会输出回答该问题所需的数值。System time 是当前相对于 NTP 时间的偏移量,后面跟着 fast 或 slow。Last offset 是最近一次校正的幅度。Frequency 是 chrony 测量出的时钟速率误差,chrony 已经在补偿该误差。Leap status 应显示为 Normal。如果显示 Not synchronised,且 Reference ID 为 00000000 (),则表示 chrony 尚未确定时间源。
chronyc sources -v 会在列表上方输出图例,因此无需记忆这些符号。列表中的两列最重要。每行开头的状态字符表示 chrony 对该时间源的评估结果,其中 * 表示 chrony 当前使用的时间源;如果每行都显示 ?,则表示没有任何时间源响应。Reach 是最近 8 次轮询的响应历史,以八进制显示:377 表示 8 次全部得到响应,0 表示一次响应也没有。
如果由 systemd-timesyncd 负责校时:
timedatectl timesync-status
timedatectl show-timesync --alltimesync-status 会输出它正在连接的服务器、轮询间隔以及 Offset 值。如果命令返回的是服务错误,而不是状态信息,则说明此机器并非由 timesyncd 负责校时。这本身就回答了你的问题。
如果不使用额外工具,想与外部时间进行粗略比较,可以将本机时钟与公共 HTTP Date 标头进行比较。该标头使用 GMT,精度为 1 秒:
date -u
curl -sI https://www.cloudflare.com | grep -i '^date:'这里相差 1 秒或 2 秒属于正常情况,没有实际意义。相差 1 分钟则说明你的配置存在问题。
VPS 上使用 chrony 还是 systemd-timesyncd
Ubuntu 和 Debian 默认安装 systemd-timesyncd。它是 SNTP(简单网络时间协议)客户端:一次查询一个服务器,并让时钟逐步接近该服务器的时间。对于持续在线且初始时间大致正确的机器,这已经足够,而且运行开销几乎可以忽略。
chrony 是完整的 NTP 实现,在虚拟机上通常是更好的默认选择,具体原因可以从它自身的输出中看到。它会同时轮询多个时间源,并丢弃时间不一致的源。它会测量本机时钟的速率误差,并将结果写入漂移文件,因此会修正时钟本身的偏差,而不是追逐每个采样值。对于虚拟机特有的两种情况,它也能快速恢复,而物理服务器不会遇到这些情况:虚拟机可能被宿主机暂停,也可能在运行期间迁移到其他宿主机。如果宿主机提供了 PTP 设备,则由 chrony 读取该设备。
sudo apt update && sudo apt install -y chrony
systemctl status chrony --no-pager
chronyc sources -v
chronyc tracking安装过程中请查看 apt 输出。在 Debian 和 Ubuntu 上,chrony 和 systemd-timesyncd 软件包都会提供 time-daemon,因此 apt 会在安装 chrony 时删除 timesyncd。这是正确且符合预期的行为。不要同时运行两者,因为两个守护进程会同时设置同一个时钟并相互冲突;在这种情况下,任何一个守护进程报告的偏移量都不可信。在 Rocky 和 AlmaLinux 上,使用 sudo dnf install -y chrony 安装,服务单元名称为 chronyd,而不是 chrony。
Debian 和 Ubuntu 的配置文件是 /etc/chrony/chrony.conf,Rocky 和 Alma 的配置文件是 /etc/chrony.conf。发行版默认配置已经适合 VPS,因此只有在有明确原因时才修改。以下两个配置项值得了解:
pool和server行用于指定时间源。添加iburst后,chrony 会在启动时快速连续发送请求,因此首次同步会在几秒内完成,而不是等待几分钟。makestep决定 chrony 何时直接跳变时钟,而不是逐步调整。使用grep -n makestep /etc/chrony/chrony.conf查看当前配置。Debian 和 Ubuntu 的默认值makestep 1 3表示:chronyd 启动后的前 3 次更新中,如果时钟偏差超过 1 秒,就直接校正;此后只通过逐步调整进行校正。
如果希望对抗链路上的篡改,并对时间流量进行身份验证,chrony 4 及更高版本支持 NTS(网络时间安全)。先使用 chronyd -v 确认版本,并注意 NTS 除了 UDP 123 外,还需要开放出站 TCP 4460 端口:
server time.cloudflare.com iburst nts重启服务并完成验证后,再依赖其提供的时间。配置解析失败会导致系统完全没有时间同步守护进程,而时钟本身不会提示这一问题。
sudo systemctl restart chrony
chronyc sources -v
chronyc tracking为什么相差几分钟的时钟会一直不准
时间守护进程有两种方式修正偏差。平滑调整会加快或减慢时钟,直到误差消失。这样可以保持时间持续向前,不会重复或跳过时间戳。直接校时会立即跳到正确值。它速度更快,但可能让时钟回拨。对于依赖墙上时钟测量经过时间的软件,回拨会造成风险。因此,两个守护进程都优先使用平滑调整。
这也是严重不准的时钟可能长时间保持错误的原因。chrony 只会在 makestep 允许的窗口内直接校时。默认情况下,该窗口仅存在于守护进程启动后的前几次更新中。chronyd 已运行一周后才发现相差 40 秒时,会通过平滑调整进行修正,而平滑调整 40 秒所需的时间远超出通常可接受的等待时间。请在系统负载较低时,有意执行一次直接校时:
sudo chronyc makestep
chronyc tracking此时,chronyc tracking 应报告接近零的 System time 偏差,Last offset 应显示刚刚修正的偏差大小。在繁忙的数据库主机上执行前请谨慎考虑,因为时钟回拨可能使假定时间只会向前移动的软件产生混乱。重启守护进程是同一修复方式的温和版本,因为启动时 makestep 窗口会重新打开。
容器共享主机时钟
容器没有自己的挂钟,因此无需在容器内同步时间。Linux 时间命名空间只会虚拟化单调时钟和启动时间时钟。CLOCK_REALTIME 不会被虚拟化,因此容器读取的是运行它的主机的同一个系统时钟。修正主机时钟后,该主机上的所有容器都会同时得到修正。
这会带来一些后果。不要在镜像中安装 chrony 或 ntpd,因为最好的结果也是不起作用。在非特权容器中设置日期会失败,并返回 date: cannot set date: Operation not permitted,因为内核要求该调用具备 CAP_SYS_TIME。授予 CAP_SYS_TIME 不会为容器提供独立时钟,而是允许容器修改主机时钟,从而同时修改其他所有容器的时钟。
容器使用不同的时区不是时钟问题。包含自身 /etc/localtime 的镜像会将同一时刻按另一个时区格式化,因此 date 看起来不正确,但时钟本身是正确的。在容器环境中设置 TZ=UTC 即可消除混淆。所选的运行时不会改变这一点;无 root 的 Podman 与 Docker 对比介绍了它实际会改变的内容。
时区:服务器使用 UTC,人员按本地时间查看
将机器设置为 UTC,并保持不变。
timedatectl list-timezones | grep -i utc
sudo timedatectl set-timezone UTC
timedatectlUTC 没有夏令时,这就是全部理由。在实行夏令时的时区中,将每日任务设为 02:30 后,时钟回拨当天会运行两次,时钟前拨当天则完全不会运行。man 8 cron 说明了少于三小时的时间调整如何特殊处理:因时间前拨而跳过的任务会在调整后不久运行,因时间回拨而落入重复小时的任务不会再次运行。这种行为是合理的,但您不应在 03:00 还需要推断它会如何运行。使用 UTC 后,任务在一年中的每一天都只运行一次。如果您的任务是完全没有运行,而不是在异常时间触发,那么更可能的原因是cron 任务静默不运行的原因。
读取日志时同样如此。journalctl 使用系统时区格式化时间戳,journalctl --utc 强制使用 UTC。位于不同时区的两台服务器会让每次故障排查都变成时间换算;而在压力下进行换算,容易导致时间线判断错误。让系统使用 UTC,以 UTC 存储时间戳,并在人员查看时统一转换一次。任何人如需在单条命令中使用本地时间,都可以单独指定,无需更改机器设置:
TZ=Europe/Berlin datetimedatectl 输出中还有一行属于本节。RTC in local TZ 应显示为 no。将其设置为 yes 是在笔记本电脑上与 Windows 双启动时使用的解决方法;在服务器上,这只会增加一个以后可能造成混淆的偏移量。设置该项后,timedatectl 会输出警告,说明系统已配置为使用本地时区读取 RTC 时间。
按症状排查
一台服务器拒绝您的双因素验证码。 先检查时钟,再检查其他内容。验证码来自每 30 秒递增一次的计数器,因此服务器落后 90 秒时,会根据手机已经经过的时间步计算验证码。timedatectl 将显示 System clock synchronized: no,或者 chronyc tracking 将报告较大的 System time 偏移量。这与密钥被直接拒绝是不同的问题。后者会输出单独的错误消息,相关内容见公钥身份验证失败排查指南。
apt update 提示 Release 文件尚未生效。 完整消息会列出软件源以及它还将无效多长时间,例如 is not valid yet (invalid for another 1d 2h 3min 4s)。您的时钟落后于软件源 Release 文件中的日期,该时长直接表示时钟落后的程度。修正时钟。不要禁用 apt 的日期检查来绕过此问题,因为该检查可以防止他人向您提供过期的软件包索引。
每个源都显示不可达状态,且 Reach 为 0。 没有任何响应,因此应检查出站网络,而不是配置。NTP 使用出站 UDP 端口 123,某些网络会过滤或重定向此流量。sudo chronyc ntpdata 会输出每个源的计数器,包括 Total TX 和 Total RX。如果 TX 计数持续增加而 RX 保持为 0,说明数据包已经发出但没有返回,这通常表示您与源之间的防火墙阻止了流量。
时钟原本正确,随后发生跳变。 主机事件可能导致这种情况。恢复快照、暂停来宾系统,或将其实时迁移到另一台主机,都可能使来宾系统的时间落后于实际时间。chrony 会在下一次轮询时发现问题并进行校正;systemd-timesyncd 可能会先等待较长的轮询间隔。使用 systemctl is-enabled chrony 确认守护进程会在启动时启动,因为手动启动的守护进程会在下一次重启后退出。
偏移量很小,但始终无法稳定。 检查 CPU steal。来宾系统在定时器中断到期时未获得调度,其采样就会延迟,因此偏移量会持续波动,而不是逐渐收敛。top 会在 CPU 行中以 st 数值显示此情况。在共享主机上读取 CPU steal 时间介绍该数值的含义以及相应的处理方法。
刚签发的证书被拒绝,并提示尚未生效。 curl 会输出 SSL certificate problem: certificate is not yet valid,浏览器也会显示类似消息。证书本身没有问题,检查证书有效期的时钟落后了。客户端和服务器都可能是问题所在,因此请同时检查两端。如果签发证书的服务器时钟错误,certbot 和 nginx 证书指南介绍了同一配置下的续期处理。
将其加入现有检查项
时间同步是启动时设置的问题,可能在数月后静默失效。这正是例行检查能够发现、而记忆无法可靠保留的问题。timedatectl 和 chronyc tracking 放在一起查看只需两秒。将它们作为新 VPS 上前 10 分钟内的检查的一部分运行,并在执行Linux 服务器例行维护检查清单时再次运行。如果希望检查自动运行,并在偏移量增大时发出告警,请参阅编写 systemd 服务和计时器,其中介绍了让小型单元按计划报告的模式。此类检查是短脚本,而不是守护进程,因此应使用 Type=oneshot,而不是默认设置;systemd 服务类型概览解释了为什么使用错误的类型会导致单元报告并未真正获得的成功。
FAQ
如何检查 VPS 时钟是否同步?
运行 timedatectl,然后读取 System clock synchronized 行。这是内核自身的标志,由负责校准时钟的守护进程设置。因此,在使用 chrony 的机器上,yes 与 NTP service: n/a 同时出现是正常且健康的。要查看误差大小,运行 chronyc tracking 并读取 System time;如果由 systemd-timesyncd 负责同步,则运行 timedatectl timesync-status 并读取 Offset。如果要与机器外部的时间进行核对,请将 date -u 与任意 HTTPS 站点返回的 Date 标头进行比较。
在 VPS 上应该使用 chrony 还是 systemd-timesyncd?
对重要服务器使用 chrony。systemd-timesyncd 是一个只跟随单个服务器的 SNTP 客户端,适合持续在线且初始时间接近正确值的机器。chrony 会轮询多个时间源,拒绝不一致的时间源,学习本机时钟的速率误差,并在主机暂停或实时迁移后快速恢复。在 Debian 或 Ubuntu 上安装 chrony 会自动移除 systemd-timesyncd,因为这两个软件包都提供 time-daemon。不要同时运行两个时间同步守护进程。
为什么 TOTP 代码在一台服务器上失败,但在其他地方都正常?
因为 TOTP 代码取决于当前时间。代码来自每 30 秒递增一次的计数器,因此服务器和手机必须对当前所处的时间步长达成一致。大多数验证器会同时接受前后各一个时间步长,因此每个方向大约有半分钟的容差。检查该服务器上的 timedatectl。如果 System clock synchronized 读取为 no,请修复时间同步,代码就会再次匹配,无需更改共享密钥。
可以在 Docker 容器内设置时间吗?
不可以,也没有必要。容器共享主机的 CLOCK_REALTIME,因为 Linux 时间命名空间只会虚拟化单调时钟和启动时间时钟。非特权容器会获得 date: cannot set date: Operation not permitted;添加 CAP_SYS_TIME 后,容器可以修改主机时钟,而不是获得自己的时钟。请改为同步主机时间。容器内显示不同的本地时间属于时区设置,因此请在容器环境中设置 TZ。
服务器应该使用 UTC 还是本地时间?
使用 UTC,在人员读取输出时再应用本地时间。UTC 不会因夏令时发生变化,因此每日任务全年每天只运行一次,不同服务器的时间戳也无需转换即可对齐。使用 sudo timedatectl set-timezone UTC 设置 UTC。需要查看本地时间时,可以为单条命令添加前缀,例如 TZ=America/New_York date;这不会改变系统时钟。