Linux 服务器时间同步实战:chrony 配置、时钟偏移诊断与容器/虚拟机时间陷阱

服务器时间差 5 分钟,我的 HTTPS 证书就报错了

这是一个真实发生过的事故。某台 VPS 因为宿主机迁移,BIOS 时间偏了,重启后系统时钟直接跑到了未来。结果是:

  • Let's Encrypt 证书校验失败,报 "certificate is not yet valid",站点全红。
  • MySQL 主从复制报错,因为 binlog 时间戳乱序。
  • 所有日志时间戳全错,出问题时根本没法按时间线排查。
  • 搜索爬虫和一部分 API 调用出现签名校验失败。

而时钟偏移这件事,在不重启的机器上会悄悄发生——虚拟化平台的时钟漂移、CPU 节能降频、负载过高导致时钟中断丢失,都会让时钟越走越偏。你可能几个月都没发现,直到某个依赖精确时间的组件(证书、签名、数据库)突然炸掉。

这篇文章讲清楚:现代 Linux 上该用 chrony 还是 ntpd,怎么判断时钟是否可信,以及那些真正会咬人的配置细节。

一、先确认你的服务器时间到底准不准

在折腾任何配置之前,先做一次体检。这几个命令按顺序执行:

# 1. 看当前时间与时区
timedatectl

# 2. 看时间同步服务的实际状态
timedatectl status

# 3. 直接和公共时间源对比,看偏差多少秒
chronyc tracking        # chrony 环境
ntpq -p                 # ntpd 环境

# 4. 用 HTTP Date 头做粗略交叉验证
date -u; curl -sI https://www.google.com | grep -i '^date:'

timedatectl 的输出里,重点看三行:

               Local time: Sun 2026-09-21 08:30:12 UTC
           Universal time: Sun 2026-09-21 08:30:12 UTC
                 RTC time: Sun 2026-09-21 08:30:11 UTC
                Time zone: UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

System clock synchronized: yes 必须是 yes。如果是 no,说明时钟没有和任何可信时间源同步,你看到的系统时间完全来自硬件,可能已经偏了几十秒甚至更多。这是第一优先要修的问题。

RTC in local TZ: no 也必须是 no。硬件时钟应该存 UTC,如果它存的是本地时间,会在夏令时切换或跨时区迁移时产生一小时的跳变。上一条命令里如果看到 yes,用 timedatectl set-local-rtc 0 改回来。

chronyc tracking 的输出更精确,重点字段是 System time

$ chronyc tracking
Reference ID    : A1B2C3D4 (ntp1.example.com)
Stratum         : 2
Ref time (UTC)  : Sun Sep 21 08:29:58 2026
System time     : 0.000023456 seconds fast of NTP time
Last offset     : +0.000031278 seconds
RMS offset      : 0.000089123 seconds
Frequency       : 12.345 ppm slow
Residual freq   : +0.002 ppm
Skew            : 0.015 ppm
Root delay      : 0.021345678 seconds
Root dispersion : 0.000123456 seconds
Update interval : 64.2 seconds
Leap status     : Normal

判读方法:

  • System time 就是你相对参考源的真实偏差。单位是秒。小于 0.001 秒(1 毫秒)算优秀,0.001~0.05 秒算正常,超过 1 秒要警觉,超过 10 秒基本肯定有硬件或虚拟化问题。
  • Leap status 必须是 Normal。如果是 Not synchronised,说明根本没同步上,检查网络到上游 123/UDP 是否通。
  • Stratum 是层级,1 是原子钟直接源,2~4 正常。如果 Stratum 显示 16 或很大的数字,说明上游不可用。
  • Skew 是频率估计的误差。持续很高(>100ppm)说明你的时钟晶振不稳,虚拟机上常见。

二、chrony vs ntpd:为什么现代系统应该选 chrony

Debian 9+、Ubuntu 18.04+、RHEL/CentOS 7+ 默认都换成了 chrony。它相比老 ntpd 有实质优势,不是口味问题:

  • 收敛快得多。 ntpd 需要几小时甚至一天才能把时钟调准;chrony 通常几分钟内就能收敛到毫秒级。对经常重启的 VPS 和容器来说这是决定性的。
  • 能处理断续网络。 ntpd 假设网络持续可用,网络断一段时间后要重新慢慢收敛。chrony 会记录时钟频率,即使长时间断网,也能靠历史数据把时间维持得相当准。
  • 频率校正更平滑。 chrony 用更先进的算法估算晶振频率,避免"调一下、过冲、再调回来"的振荡。
  • 默认就是好配置。 开箱配置基本可用,不像 ntpd 需要仔细挑选 server 列表。

所以除非你在跑必须用 ntpd 的遗留环境,统一选 chrony。

安装与基础配置

# Debian / Ubuntu
apt update && apt install -y chrony

# 确认服务起来并开机自启
systemctl enable --now chrony
systemctl status chrony --no-pager

配置文件在 /etc/chrony/chrony.conf(RHEL 系是 /etc/chrony.conf)。一份适合国内或国际 VPS 的配置:

# 上游时间源。国内机器优先用国内源,延迟低、更稳定
pool ntp.aliyun.com iburst
pool ntp1.aliyun.com iburst
pool time.cloudflare.com iburst
pool pool.ntp.org iburst

# 首次同步后允许时钟跳变(限前几次)
makestep 1.0 3

# 保存时钟频率数据,重启后快速恢复精度
driftfile /var/lib/chrony/chrony.drift

# 开启实时时钟(RTC)的定期同步
rtcsync

# 只允许本机客户端,不给外部提供时间服务
allow 127.0.0.1

# 日志
logdir /var/log/chrony

几个参数必须解释清楚,因为配错会导致同步失败或时间跳变:

  • iburst:启动时快速发包(间隔 2 秒连发 4 次),而不是常规的 64 秒一次。没这个参数,初始同步要等几分钟。这是最容易漏的一个。
  • makestep 1.0 3:前 3 次同步时,如果偏差超过 1 秒,直接跳变校正;之后改为缓慢调整(slew)。这个默认行为非常合理——启动时立刻纠正,运行时避免时间倒退影响应用。
  • rtcsync:让内核定期把系统时间写回硬件时钟。没有它,重启后会从偏掉的 RTC 起步,再等待同步。开了它,重启后起点就基本是准的。
  • pool 而不是 server:pool 会自动解析出一个域名下的多个 IP 并动态选择质量最好的几个。域名只有一个 IP 时才用 server。

关于 makestep 的运行时行为要小心

如果运行过程中时钟偏差突然超过阈值(比如宿主机被暂停又恢复,时间一次性跳了几十秒),chrony 默认会以极慢的速度(slew,约每秒最多 0.005 秒)逐步纠正——纠正 30 秒需要约 100 分钟。这段时间内时间戳仍然是不准的。

如果你跑的是数据库主从、或者签名的 API,这种慢纠正是灾难。可以这样处理:

# 运行时也允许跳变,但阈值设得较高,避免频繁跳
makestep 1000 1

或者需要立即纠正时手动执行:

chronyc makestep

对普通个人站来说,默认配置足够了。只有跑 MySQL 主从、Kafka、证书签发服务这类对时序敏感的组件,才需要精心设计这一块。

三、容器与虚拟机的时间陷阱

这是现代部署里最大的坑,而且绝大多数教程不讲。

1. Docker 容器默认继承宿主机时钟

容器没有独立的内核时钟,它读的就是宿主机的系统时钟。所以给容器装 NTP 是没用的(而且通常做不到,因为容器没有 CAP_SYS_TIME 权限)。正确的做法只有一个:在宿主机上把时间同步做好

# 宿主机上检查
timedatectl
chronyc tracking

# 容器内验证(应该和宿主机一致)
docker run --rm debian:12 date -u
date -u

如果容器时间和宿主机不一致,那是别的问题(比如镜像里写了假时间,或时区配置不同)。注意时间戳时区显示是两回事:容器可能 UTC 正确但显示成别的时区。

2. 时区配置:UTC 还是本地时区

强烈建议服务器统一用 UTC。理由:

  • 日志跨机器对比时不会错乱(不同机器设不同时区是排查噩梦)。
  • 夏令时切换不会导致时间跳变和定时任务重复/缺失执行。
  • 数据库、日志、监控全用 UTC,只在展示层做时区转换。
timedatectl set-timezone UTC

# 容器里如果需要特定时区
docker run -e TZ=Asia/Shanghai --rm debian:12 date

但有一个具体场景必须注意:如果你的 crontab 是按"北京时间 8 点"写的,服务器改成 UTC 后会变成凌晨 0 点执行。 改时区前一定要检查所有 crontab 并换算。

3. 虚拟化平台的时钟漂移

KVM/Xen/VMware 上的虚拟机,在宿主机高负载时时间会明显滞后。除了跑 NTP,还应该确认装了虚拟化增强工具:

# KVM/Hyper-V 检查 kvm-clock
dmesg | grep -i -E 'kvm|clocksource'
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
cat /sys/devices/system/clocksource/clocksource0/available_clocksource

# 应该优先使用 kvm-clock / tsc,而不是 hpet / acpi_pm

如果 current_clocksource 是 hpetacpi_pm,那你的时钟精度天生就差。可以尝试切换到 tsc:

echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource
# 持久化需在 grub 加 clocksource=tsc

注意:只在确认 CPU 支持 invariant TSC 且已装 virtio 驱动时改,否则可能反而更不稳。改完要观察 24 小时的 chronyc tracking

四、把时间纳入监控

最好的做法不是"出问题时去查",而是让偏移在超阈值时主动告警。chrony 自带的 chronyc 输出很容易接进监控系统。

用一行 shell 做简单监控

#!/bin/bash
# /usr/local/bin/check-clock.sh
# 返回非零表示时钟偏移超阈值
MAX_OFFSET=0.5   # 秒

offset=$(chronyc tracking | awk '/System time/ {print $4}')
# 取出绝对值
abs=$(echo "$offset" | sed 's/^-//')

if command -v bc >/dev/null 2>&1; then
    over=$(echo "$abs > $MAX_OFFSET" | bc)
else
    over=$(awk -v a="$abs" -v m="$MAX_OFFSET" 'BEGIN{print (a>m)?1:0}')
fi

if [ "$over" = "1" ]; then
    echo "CLOCK OFFSET DANGER: ${offset}s (limit ${MAX_OFFSET}s)"
    exit 1
fi
echo "clock ok: ${offset}s"
exit 0

挂到 crontab 里每小时跑一次,异常时由已有告警通道发出:

0 * * * * /usr/local/bin/check-clock.sh || /usr/local/bin/alert.sh "时钟偏移异常"

Prometheus 环境

node_exporter--collector.ntp(或 chrony_exporter),关注这两个指标:

node_ntp_offset_seconds      # 偏移量,告警阈值建议 0.5
node_ntp_sanity              # 必须为 1,表示同步正常运行

规则示例:

alert: ClockOffsetTooLarge
expr: abs(node_ntp_offset_seconds) > 0.5
for: 10m
labels:
  severity: warning
annotations:
  summary: "服务器时钟偏移超过 0.5 秒"

五、故障速查表

把常见症状和对应的动作列在一起,出问题时直接对号入座:

  1. System clock synchronized: no → 检查 123/UDP 是否被防火墙/云安全组封了。很多云厂商默认封 UDP 123 出站。chronyc sources -v 看上游状态,^? 表示不可达。
  2. 上游全部显示 ^? → 网络或防火墙问题。临时测试:chronyd -Q 'server ntp.aliyun.com iburst'(-Q 表示只查询一次然后退出,用于排错最方便)。
  3. 时间偏移巨大且缓慢纠正 → 手动 chronyc makestep 立即跳变。但要先确认没有依赖单调递增时间的应用在运行。
  4. 时间在重启后回到过去 → 检查 rtcsync 是否开启,以及 BIOS/RTC 是否走偏(hwclock --show)。
  5. 定时任务重复执行或漏执行 → 时钟被向后调过。这是为什么禁止在运行中大幅回调时间,必须用 makestep 一次跳到位。
  6. 证书报 "not yet valid" → 时钟跑到了未来。先修时间,再重新签证书。注意这种情况下签发的证书本身可能是错的,必须重签而不是重试。
  7. 容器时间不对 → 别在容器里装 NTP,去宿主机修。容器改时间需要 --privileged 且会影响宿主机全局,风险极大。

小结

时钟同步是一件"做好了两三年都不会想起它,没做好就到处出怪事"的基础设施。它同时牵扯 TLS、数据库、日志、分布式协调和爬虫交互,是最典型的隐形依赖。

行动清单只有四条:安装 chrony;确认 timedatectl 里 synchronized 是 yes 且 RTC 走 UTC;把服务器时区统一成 UTC 并检查 crontab;最后加一条偏移量监控。花不到二十分钟,能省掉未来某天深夜排查"为什么证书突然无效"的一整个晚上。

Last modification:September 21st, 2026 at 08:24 pm

Leave a Comment