VPS时间漂移怎么查和修复
VPS时间漂移会导致2FA验证码失败、证书报错和计划任务错时。本文教您检查三个时钟,解读 chronyc 与 timedatectl 输出,并修复时间同步。
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 秒的宽限期,因此时钟快1秒就会拒绝本应完全有效的证书。
三个时钟,以及真正重要的时钟
系统时钟才是关键。它是内核的 CLOCK_REALTIME:自 1970 年 1 月 1 日 UTC 起经过的秒数,保存在内存中,由所有需要记录时间的组件读取。日志行、证书检查、TOTP 代码和文件修改时间都来自系统时钟。当有人说服务器时间不正确时,指的就是这个时钟。
硬件时钟也称 RTC(实时时钟),是一个在机器关机期间仍持续运行的独立计数器。在物理机上,它是由电池供电的芯片。在虚拟机中,它由 hypervisor 模拟,因此基本上反映的是主机的时间。在启动时,Linux 读取一次硬件时钟,将其作为初始值,然后维护自己的计数。timedatectl 会在 RTC time 行显示它。在 VPS 上不要根据这一行进行故障排查,因为它反映的是主机对时间的认知,而不是系统时钟的同步状态。在容器中通常根本没有 /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。它读取由主机维护的值,因此即使 KVM 客户机完全没有 NTP 客户端,时间在一段时间内仍会大致准确。tsc 是 CPU 自己的计数器。Xen 客户机会报告 xen,Hyper-V 客户机会报告 hyperv 时钟源。除非有经过测量的明确理由,否则不要修改此设置,因为内核已经会为当前硬件选择它信任的最佳时钟源。
某些主机还会将 PTP(精确时间协议)设备提供给客户机,使 chrony 能够直接读取主机时钟,而不必通过网络读取。建议检查该设备,但共享 VPS 通常无法使用:
sudo modprobe ptp_kvm
ls /dev/ptp*
cat /sys/class/ptp/ptp0/clock_name如果 modprobe 失败,或没有显示任何设备,说明主机未提供该设备,此时应使用网络 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 对该时间源的评估,其中 * 表示当前正在使用的时间源;如果每一行都显示 ?,表示没有任何时间源响应。Reach 是最近八次轮询的响应历史,以八进制显示:377 表示八次全部收到响应,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 启动后的前三次更新中,如果时钟偏差超过一秒,则直接校准;之后只通过逐步调整来修正。
如果希望对抗链路上的篡改,并对时间流量进行身份验证,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 已运行一周,之后才发现时间相差四十秒,就会通过平滑调整来修正,而修正四十秒所需的时间可能远超可接受的等待时间。请在系统较空闲时,有意执行一次强制修正:
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 即可消除混淆。所选的运行时不会改变这一点;rootless Podman 与 Docker 的比较介绍了它实际会改变的内容。
时区:服务器使用 UTC,人员使用本地时间
将机器设置为 UTC,并保持不变。
timedatectl list-timezones | grep -i utc
sudo timedatectl set-timezone UTC
timedatectlUTC 没有夏令时,这就是全部理由。在实行夏令时的时区中,设置为每天 02:30 运行的任务会在时钟回拨当天运行两次,在时钟前拨当天则完全不会运行。man 8 cron 说明了时间调整幅度小于 three hours 时的特殊处理:因时间前拨而跳过的任务会在变更后不久运行,因时间回拨而落入重复小时内的任务不会再次运行。这种行为是合理的,但您不应在 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 上线后前十分钟内的检查的一部分,并在执行常规 Linux 服务器维护检查清单时再次检查。如果您希望检查自动运行,并在偏移量增大时发出告警,编写 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 进行设置。需要查看本地时间时,可以为单条命令添加前缀,例如 TZ=America/New_York date;这不会改变系统时钟。