
前阵子有个朋友找我说他们一台 Ubuntu 服务器上的日志怎么也对不上号应用日志比数据库日志早了四十多秒排查了半天以为是程序 bug最后发现是 NTP 时间同步压根没生效。这种事在运维圈里太常见了系统装完看着能跑date命令也能输出时间但真正到了多机协作、日志比对、证书校验、集群选举这些场景几秒钟的偏差就足以把整个链路搞崩。这篇就把 Ubuntu 下开启 NTP 时间同步这件事从头到尾捋一遍从最基本的三个时钟概念讲起到 systemd-timesyncd 和 chrony 两套方案的配置实操再到内网自建 NTP 服务器给整个机房发时间最后配上我这些年踩过的坑和排查思路。不管你是刚接触 Ubuntu 的新手还是带着几十台机器要统一时间的老运维都能在里面找到能直接抄的部分。1. Ubuntu 时间同步到底在解决什么问题1.1 从一次日志对不上号的排查说起先说我那位朋友遇到的情况典型的时间漂移现场。他们的架构是两台应用服务器加一台数据库三台机器的系统时间各自为政谁也没配 NTP。Ubuntu 装完之后默认会启用 systemd-timesyncd 去尝试同步但如果安装时网络不通或者云厂商的镜像里把这个服务关掉了系统就会一直用 CMOS 里那个精度极差、每天能漂几秒的时钟。跑上一个月三台机器之间差出几十秒完全不奇怪。这种偏差平时看不出来一旦出问题就很要命。用户下单的时间戳落到了应用服务器上订单写进数据库时又打了一次时间戳两个时间差了几十秒做对账的时候数据就穿越了。更麻烦的是排查过程本身你打开两台的日志并排看会发现请求和响应的时间顺序完全乱套人的直觉会先怀疑代码逻辑把真正的原因往后拖了几个小时。所以时间同步这件事的价值不在时间准不准本身而在于让所有机器对同一个时刻有同一个认知。绝对时间是否精确到毫秒对大部分业务来说没那么重要重要的是机器 A 说现在是 10:00:00的时候机器 B 也说现在是 10:00:00误差控制在你能接受的范围内。1.2 系统时钟、硬件时钟、单调时钟先分清这三个概念很多人配 NTP 配不明白是因为没搞清楚 Ubuntu 里其实存在好几个时间。我把它们拆开讲。系统时钟System Clock也就是内核维护的那个时间date命令看到的就是它。它完全靠软件运行断电就没了但精度高、可以被 NTP 调整。所有进程取时间、写日志时间戳用的都是它。硬件时钟RTC / CMOS Clock主板上一块小电池供电的芯片断电之后还在走。开机时内核会从它读一个初始值来设置系统时钟。问题是这块芯片普遍走不准尤其在便宜的主板和虚拟机上一个月漂几分钟是常态。NTP 同步的本质就是不断修正系统时钟然后定期把它写回硬件时钟。单调时钟Monotonic Clock这个容易被忽略。它从系统启动开始计时只增不减不受 NTP 调整影响。clock_gettime(CLOCK_MONOTONIC)拿到的就是它。为什么要有它因为如果程序用系统时钟来算这个操作耗时多久一旦 NTP 往回跳了 10 秒算出来的耗时可能就是负数。所以凡是测耗时、做超时控制的场景都该用单调时钟。理解这三者的关系你就明白为什么 NTP 同步之后还要执行一次hwclock --systohc——那是把已经被校准的系统时钟回写到硬件时钟保证下次开机时起点不至于偏太远。1.3 时间跑偏会连累哪些业务时间偏差带来的问题按严重程度排一下我觉得是这样几档。最轻的一档是日志乱序前面说过了纯粹影响排查效率。往上一档是分布式系统的判定逻辑出问题Kafka 的消息时间戳、Elasticsearch 的索引排序、分布式追踪链路里的 span 顺序都依赖各节点时间基本一致偏差大了链路就拼不起来。再往上一档是安全认证类场景Kerberos 默认允许的时钟偏差是 5 分钟超了直接拒绝认证带时间戳签名的接口、JWT 的过期校验、TLS 证书的有效期判断都吃时间准确性。最狠的一档是集群自身的健康判定某些数据库和高可用组件靠心跳超时来判断节点存活如果节点间时间跳变可能误判对方挂了触发不必要的切换。还有一类容易被忽略的是只写一次、事后无法回头的记录。比如审计日志、区块链类应用的出块时间、工单系统的创建时间这些数据一旦带着错误的时间戳落库后面想修正就得改历史数据代价极高。这也是为什么我坚持新机器上线第一件事就是确认时间同步状态而不是等到出问题再补。2. 四种时间同步方案怎么选2.1 systemd-timesyncd装完系统就自带的那个Ubuntu 从 16.04 起就默认带 systemd-timesyncd它是 systemd 项目里的一个轻量 NTP 客户端只做客户端不做服务端代码量很小功能也简单。它的定位就是让普通工作站和轻量服务器有个基本准的时间不追求毫秒级精度也不提供复杂的统计和监控能力。它的配置文件在/etc/systemd/timesyncd.conf默认内容几乎是全注释的走的是 Ubuntu 官方的时间源。对个人开发机、测试环境、跑在云上的普通应用服务器来说这东西够用而且零安装成本。它的局限也很明确不能对外提供时间服务、调整策略比较死板、虚拟机环境下应对时钟漂移的能力偏弱排查问题时能看到的诊断信息也少。我的一般原则是单机、非关键业务用 systemd-timesyncd一旦涉及多机协作、内网统一时间源、或者对精度有要求直接换 chrony。2.2 chrony生产环境我的默认选择chrony 是目前 Linux 上最主流的时间同步实现Ubuntu 官方仓库里直接有包。它相对老牌 ntpd 的优势体现在几个方面。第一是收敛速度快。chrony 的算法能在几次轮询内就把本地时钟的频率偏差估计得比较准ntpd 往往需要几小时甚至一天才能达到同样的精度。第二是对断续网络和虚拟机的适应性强它会根据实际观测动态调整轮询间隔网络抖动时不会像 ntpd 那样反应迟钝。第三是支持手动立即校准配置里的makestep参数允许在启动初期把偏差一次性跳过去而不是花几天慢慢磨这在虚拟机里特别有用。第四是它同时能做客户端和服务端一台机器同步上游之后可以顺手对内网发布时间不需要再装别的软件。它有两个进程chronyd是后台守护进程chronyc是命令行工具。这个设计比 ntpd 的ntpq好用不少输出更直观。2.3 ntpd 和 ntpdate老方案为什么被劝退ntpd 是 NTP 协议的经典实现稳定性经过了几十年验证至今在一些老系统上还在跑。但对新部署的 Ubuntu 机器我不太建议再用它。原因是它的收敛速度慢、虚拟机环境下表现一般、配置文件的风格也比较老派。当然如果你的环境里已经有一整套基于 ntpd 的运维体系继续用也没问题别为了换而换。ntpdate 则要单独说一句这个东西已经明确不推荐用于生产了。它是一个一次性校时工具执行一次就把系统时间硬跳过去不持续运行。问题在于硬跳时间对正在运行的程序非常不友好——数据库事务、定时任务、心跳检测都可能被这个跳变搞乱。它现在的合理用途只有一个在没有其他手段的极端情况下手动把偏差很大的机器粗调一下然后再交给 chrony 精细维护。Ubuntu 24.04 的仓库里甚至默认不再提供这个包需要单独装。顺带提一下 PTP精密时间协议。NTP 走的是普通网络栈精度通常在毫秒级PTP 需要网卡硬件打时间戳能做到亚微秒级主要用在金融交易、工业控制、音视频同步这些场景。日常的服务器运维用 NTP 就足够了PTP 属于另一个话题配置门槛和硬件要求都高得多。2.4 选型对照表把几个方案放在一张表里对比看的时候更直观。方案是否默认安装精度水平能否做服务端收敛速度推荐场景systemd-timesyncd是几十毫秒否中等个人机、轻量云主机chrony否需 apt 安装毫秒级是快生产服务器、虚拟机、内网时间源ntpd否毫秒级是慢存量老环境ntpdate否一次性否立即仅用于极端情况粗调PTP否亚微秒级是依赖硬件金融、工业、专业音视频选型还有一条隐藏规则同一台机器上不要同时跑两个时间同步服务。chrony 和 systemd-timesyncd 同时开启两个进程会互相抢着调整系统时钟结果是两边都调不好日志里还会出现互相干扰的记录。Ubuntu 在安装 chrony 时会自动把 systemd-timesyncd 停掉并屏蔽掉但还是建议装完之后手动确认一遍状态。3. 十分钟用 systemd-timesyncd 打开同步3.1 先看当前状态Ubuntu 上查时间状态一条命令就够timedatectl status输出大概长这样Local time: 四 2025-01-16 14:32:07 CST Universal time: 四 2025-01-16 06:32:07 UTC RTC time: 四 2025-01-16 06:32:07 Time zone: Asia/Shanghai (CST, 0800) System clock synchronized: yes NTP service: active RTC in local TZ: no这几行里的信息量很大逐条看。Local time是你所在时区的本地时间Universal time是 UTCRTC time是硬件时钟读出来的值。正常情况下 UTC 和 RTC time 应该基本一致误差在一两秒内如果差了好几个小时说明硬件时钟被设成了本地时间后面 3.4 节会讲怎么处理。最关键的是System clock synchronized和NTP service这两行。前者显示yes说明系统时钟已经和上游同步过了显示no就是没同步成功。后者显示active说明时间同步服务在跑显示inactive就是服务没起来。这两个字段要一起看服务 active 但 synchronized 是 no说明服务在跑但拉不到上游时间多半是网络或防火墙问题服务 inactive 那 synchronized 必然是 no。如果 NTP service 显示的是inactive先试着启动它sudo systemctl enable --now systemd-timesyncd sudo timedatectl set-ntp truetimedatectl set-ntp true这个命令的作用是让 systemd 接管系统时钟的同步开关它会自动把 systemd-timesyncd 拉起来。反过来的set-ntp false会停掉同步有些需要手动改时间的场景比如做实验模拟时间跳变会用到。3.2 改配置文件默认配置走的是 Ubuntu 官方源国内网络访问不一定顺畅。改成国内可用的公共时间源同步成功率会高很多。打开配置文件sudo nano /etc/systemd/timesyncd.confUbuntu 上的默认内容大概是这样的注意很多行是被#注释掉的[Time] #NTP #FallbackNTPntp.ubuntu.com #RootDistanceMaxSec5 #PollIntervalMinSec32 #PollIntervalMaxSec2048把NTP那一行取消注释并填上你选定的时间源多个源用空格隔开[Time] NTPntp.aliyun.com cn.pool.ntp.org FallbackNTPpool.ntp.org RootDistanceMaxSec5 PollIntervalMinSec32 PollIntervalMaxSec2048这里解释几个参数。NTP是首选时间源列表客户端会挨个尝试FallbackNTP是备选只有首选全部失败时才会用。RootDistanceMaxSec是允许的最大根距离简单理解就是离权威时间源最多允许多少延迟设成 5 秒是个比较宽松的值机房网络好的话可以调到 1。PollIntervalMinSec和PollIntervalMaxSec控制轮询间隔的上下限默认 32 秒到 2048 秒一般不用动。注意不要填127.0.0.1或者本机地址作为 NTP 源那会让客户端自己问自己形成循环。如果要让本机既做客户端又做服务端需要换 chrony 并配置local指令。改完之后还有一步很关键如果系统里同时装了 chrony需要先把它停掉否则两个服务会打架。检查一下systemctl status chronyd 2/dev/null3.3 重启与验证改完配置让服务重新读取sudo systemctl restart systemd-timesyncd然后等十几秒再查状态timedatectl status systemctl status systemd-timesyncd --no-pager同步成功的话System clock synchronized会变成yes并且 systemd 服务的日志里能看到类似Initial synchronization to time server的记录。想看更详细的同步过程用 journalctl 过滤journalctl -u systemd-timesyncd -n 50 --no-pager日志里会显示每次和哪个服务器通信、偏差是多少、下次轮询在什么时候。如果看到Timed out waiting for reply之类的字样就是网络不通或者对方没响应回到 3.2 节换源。还有个小细节systemd-timesyncd 在启动初期只会做微调slew不会直接把时间跳过去。如果你的机器时间偏差有几分钟可能要等挺久才能对上。想立刻看到效果可以先用set-ntp false关掉同步手动把时间设个大概值再开回来让它精细校准sudo timedatectl set-ntp false sudo date -s 2025-01-16 14:35:00 sudo timedatectl set-ntp true3.4 时区、硬件时钟别搞混有三个和时区相关的操作经常被搞混我分开说。设置时区用sudo timedatectl set-timezone Asia/Shanghai这个只影响本地时间的显示不影响 UTC 和硬件时钟。查可用时区列表用timedatectl list-timezones | grep Asia。硬件时钟的模式用sudo timedatectl set-local-rtc 0参数0表示硬件时钟按 UTC 存储这是 Linux 和绝大多数服务器场景的标准做法参数1表示按本地时间存储主要是为了和 Windows 双系统共存时不冲突Windows 默认把硬件时钟当本地时间。服务器上一定要用 0否则跨时区、跨夏令时的场景会出现时间错乱。把系统时间写回硬件时钟用sudo hwclock --systohc这条命令在很多教程里被一笔带过但它的意义在于NTP 只负责校准运行中的系统时钟不负责写硬件时钟chrony 配了rtcsync之后会自动做systemd-timesyncd 默认每 11 分钟同步一次到硬件时钟。如果你的机器已经校准时了执行一次这个命令下次重启的起点就不会偏太多。顺手再确认一下时区相关的环境变量别和系统时区打架echo $TZ timedatectl show-timesync --allTZ环境变量如果被设成了某个时区程序里取到的本地时间会跟着它走而不是系统时区。之前遇到过一个案例Docker 容器里因为镜像设了TZUTC容器内日志时间和宿主差 8 小时排查了好久。4. 换上 chrony 做生产级配置4.1 安装、停冲突服务安装很直接sudo apt update sudo apt install -y chronyUbuntu 的包管理脚本会自动处理冲突停掉 systemd-timesyncd 并把它 mask 掉。装完之后确认一下三件事。systemctl status chronyd --no-pager systemctl status systemd-timesyncd --no-pager timedatectl statuschronyd应该是 active (running)systemd-timesyncd应该是 inactive (dead) 或者 maskedtimedatectl里的 NTP service 显示active就行。如果 systemd-timesyncd 还活着手动处理sudo systemctl stop systemd-timesyncd sudo systemctl disable systemd-timesyncd sudo systemctl mask systemd-timesyncdmask比disable更彻底它会创建一个指向 /dev/null 的软链防止被其他服务间接拉起来。这一步不做的话将来某次重启可能两个服务同时跑调整冲突的后果就是系统时间来回跳。还要检查一下有没有残留的 ntpddpkg -l | grep -E ntp|openntpd有的话一并卸掉避免 123 端口被占。4.2 chrony.conf 关键行逐条讲Ubuntu 上 chrony 的配置文件在/etc/chrony/chrony.conf注意不是/etc/chrony.confDebian 系的路径和 RedHat 系不一样这个坑很多人踩过。默认内容里最核心的一段是时间源配置pool ntp.ubuntu.com iburst maxsources 4 pool 0.ubuntu.pool.ntp.org iburst maxsources 1 pool 1.ubuntu.pool.ntp.org iburst maxsources 1pool表示这是一个服务器池客户端会从池里解析出多个地址并从中挑选。iburst是个很重要的选项它让 chrony 在启动时用较短的间隔连续发 4 到 8 个请求而不是按正常轮询间隔慢慢来。没有iburst首次同步可能要等好几分钟有了它通常几秒到几十秒就能完成首次校准。这个参数我建议所有 server 和 pool 行都加上。把时间源换成你实际能访问的pool ntp.aliyun.com iburst maxsources 4 pool cn.pool.ntp.org iburst maxsources 4如果内网已经有自建的时间服务器直接指内网地址更靠谱server 10.0.0.10 iburst server 10.0.0.11 iburstserver和pool的区别是server指定单个具体地址pool指定一个池并允许多次解析。内网环境用server更明确公网环境用pool能享受负载和容灾。往下看其他关键指令我把常用的都列出来并解释driftfile /var/lib/chrony/chrony.drift这个文件记录本机时钟的频率偏差也就是这块晶振每天快多少秒。chrony 会把学到的偏差存下来重启之后直接读不用从头再学。不要手动删这个文件删了会导致重启后精度恢复变慢。makestep 1.0 3这条控制什么时候允许时间跳变。含义是在启动后的前 3 次校准中如果偏差超过 1 秒就直接把时钟跳过去3 次之后对所有偏差都只做渐进式微调不再跳变。对虚拟机和时间偏差大的机器这条指令几乎是必需的否则你可能要眼睁睁看着它花几小时慢慢磨。rtcsync让 chrony 每 11 分钟把系统时间同步到硬件时钟。前面手动执行hwclock --systohc的操作配了这行之后就自动完成了。logdir /var/log/chrony指定日志目录记录测量数据、统计信息。开了之后可以用chronyc的统计功能做长期分析。磁盘紧张的话可以不开。allow 192.168.1.0/24允许这个网段的主机向本机请求时间。这是把本机变成 NTP 服务端的关键指令不配allow别的机器问你要时间你会拒绝。第 5 节会详细讲。local stratum 10这个指令稍微绕一点。它的作用是即使本机没有同步上任何上游时间源也对外提供一个 stratum 10 的时间服务。意义在于容灾——如果机房出口断了上游都联系不上内网的客户端至少还有个时间源可用不至于全体失去同步。stratum 10 这个数字表示层级很低、可信度不高这样配置能保证当本机真的同步上上游之后会更优先使用上游的低 stratum 值。配置改完记得校验语法再重启sudo chronyd -Q -f /etc/chrony/chrony.conf-Q是快速退出模式它只检查配置能否正常加载不会真的启动服务很适合在重启前做一次验证。看到没有报错就可以sudo systemctl restart chronyd4.3 chronyc 两条命令看懂同步质量配完之后怎么确认它在正常工作两条命令足够。第一条是chronyc sources -v看它找到了哪些源、当前用哪个.-- Source mode ^ server, peer, # local clock. / .- Source state * current best, combined, - not combined, | / x may be in error, ~ too variable, ? unusable. || .- xxxx [ yyyy ] /- zzzz || Reach Last sample ^* ... MS Name/IP address Stratum Poll Reach LastRx Last sample ^* ntp.aliyun.com 2 6 377 35 -12us[ -45us] /- 21ms ^ 203.107.6.88 2 6 377 34 1.2ms[1.1ms] /- 30ms ^- 120.25.115.20 2 7 377 102 -3.4ms[ -3.6ms] /- 45ms这个输出的关键在左边两列。第一列是模式^表示普通服务器。第二列是状态*是当前正在使用的最佳源是备选源-表示被排除在合并计算之外?表示不可达x表示可能是错误源~表示波动太大。看到^*那一行就是当前同步的对象。右边几列里Stratum是源的层级数值越小时钟越靠近权威源2 到 4 都算正常。Reach是最近 8 次轮询的成功情况用八进制表示377就是八次全成功0就是全失败。LastRx是上次收到响应距现在多少秒。Last sample里的方括号外是最后一次测量的偏差方括号内是经过频率修正后的估计偏差。第二条是chronyc tracking看本机时钟的整体状况Reference ID : C0A8010A (ntp.aliyun.com) Stratum : 3 Ref time (UTC) : Thu Jan 16 06:35:12 2025 System time : 0.000012345 seconds fast of NTP time Last offset : 0.000008123 seconds RMS offset : 0.000024567 seconds Frequency : 12.345 ppm slow Residual freq : 0.002 ppm Skew : 0.021 ppm Root delay : 0.012345678 seconds Root dispersion : 0.001234567 seconds Update interval : 64.2 seconds Leap status : Normal这里面我最关注三个值。System time是当前系统和 NTP 时间的偏差正常应该在毫秒以下。Frequency是本机晶振的频率偏差单位是 ppm百万分之一数值在几十以内都算正常几百上千说明晶振质量差或者是虚拟机。Leap status显示Normal就对了如果显示Leap pending说明即将插入闰秒。两个命令配合着看sources -v确认有没有可用源tracking确认同步效果好不好。这两条基本能覆盖 90% 的排查需求。4.4 makestep 与时间跳变的风险控制makestep这个指令值得单独拿出来说因为它直接关系到要不要让时间跳这个有点危险的操作。时间调整有两种方式。步进step是把时钟直接设到正确值一秒钟之内完成速度快但会造成时间不连续可能踩到负数间隔。微调slew是通过略微加快或放慢时钟频率来逐渐缩短偏差时间保持连续但偏差大的时候可能要花很长时间chrony 默认的微调速率上限是 0.5 毫秒/秒也就是修正 1 秒偏差需要约 33 分钟。makestep 1.0 3的策略是只在启动阶段的头几次校准里允许步进之后就全部微调。这个设计很聪明因为机器刚启动的时候业务还没跑起来跳一下没影响等业务跑起来之后就不许跳了避免中断。但有些场景需要更激进的策略。比如虚拟机从快照恢复之后时间可能一下子落后好几天光靠微调要几年才能追回来。这时候可以在启动脚本里手动触发一次步进sudo chronyc makestep这条命令会立即执行一次步进不管当前配置是什么。在虚拟机恢复、容器迁移、系统休眠唤醒之后手动执行一下能立刻把时间拉回来。反过来对时间连续性要求极高的场景要收紧策略。比如跑数据库的机器我一般会把配置改成makestep 0.1 1意思是允许跳变的阈值收严到 0.1 秒并且只在启动的第 1 次校准里允许。偏差超过 0.1 秒也不跳宁可慢慢磨。因为数据库事务、WAL 日志、主从复制的时序对时间突然倒退是非常敏感的。提示如果业务对时间连续性有硬要求除了收紧 makestep还应该在应用层做防御——比如用单调时钟计算耗时、用逻辑时钟给事件排序、在数据库里用自增序列而不是时间戳做主键排序依据。还有一个配套参数是maxslewrate控制最大微调速率。默认是 83333 ppm大约是每秒 0.083 秒想调慢可以设成maxslewrate 500每秒 0.5 毫秒也就是 chrony 的保守模式。这个参数在 NTP 服务器上要谨慎改因为服务端需要快速响应客户端请求。5. 内网自建 NTP 服务统一整个机房的时间源5.1 为什么不让每台机器直接出网校时小环境里让每台机器各自去连公网时间源看起来最省事但机器一多问题就出来了。首先是出口压力和可靠性。一百台机器每台每分钟发一次请求就是每分钟一百个 UDP 包出去量不大但没必要。更关键的是一旦机房出口抖动或者上游时间源被限流所有机器会在同一时间失去同步而且是同时失去。集中式方案里只有时间服务器一条链路受影响内部机器的同步不受影响。其次是审计和可解释性。内网所有机器都指向同一个或一组时间服务器出了问题你能明确知道大家的时间都是谁给的排查有明确路径。各自出网的话A 机器同步的是这个源B 机器同步的是那个源两边源本身有偏差你自己都说不清哪个是对的。第三是安全边界。让内网机器主动往外发 UDP 请求意味着防火墙要开一条出站规则这个口子在一些合规要求严格的环境里是不允许的。集中式方案只需要时间服务器这一台或几台有出网权限。还有一点是性能分层。内网 NTP 服务器同步好之后对内提供服务时延迟极低通常在亚毫秒客户端更容易收敛到高精度。跨公网同步的延迟波动大客户端能达到的精度也差。5.2 服务端 allow 与 local stratum选一台网络位置合适、长期开机的机器做内网时间服务器按第 4 节的方式配好 chrony客户端部分照抄。然后加上服务端相关的配置。第一组是权限控制allow 192.168.1.0/24 allow 10.10.0.0/16allow后面跟的是网段也可以写成单台主机。没配 allow 的网段来请求会被拒绝这是 chrony 的默认安全策略比 ntpd 的默认行为要收敛得多。第二组是容灾配置local stratum 10前面解释过这里补充一个细节local stratum 10和allow配合使用时如果本机有上游源且同步正常那么对外服务用的是上游的 stratum 值比如 2 或 3如果上游全断了本机就退化成 stratum 10 继续对内服务。这就是断网也不断时间的效果。一个完整的服务端配置片段长这样pool ntp.aliyun.com iburst maxsources 4 pool cn.pool.ntp.org iburst maxsources 4 driftfile /var/lib/chrony/chrony.drift rtcsync makestep 1.0 3 logdir /var/log/chrony allow 192.168.1.0/24 allow 10.10.0.0/16 local stratum 10配好之后重启然后务必在本机确认chronyc tracking显示已经同步上上游再开始让客户端指过来。服务端自己都没校准好就对外发时间等于把错误扩散到全网这是最危险的操作。5.3 放行 123/udp 与客户端指向NTP 用的是 UDP 123 端口。服务端需要放行入站客户端需要允许出站。用 ufw 的话sudo ufw allow 123/udp sudo ufw reload sudo ufw status | grep 123用 nftables 的话在对应表里加规则sudo nft add rule inet filter input udp dport 123 accept如果是云主机除了本机防火墙还要检查安全组。云厂商的安全组是在宿主机层面做的本机 iptables 放行了但安全组没放照样不通。这个坑我踩过不止一次排查的时候会先在本机tcpdump看包有没有进来确认是安全组还是本机防火墙的问题sudo tcpdump -i any -n udp port 123 -c 20在服务端执行这条命令同时让客户端发一次请求如果有包进来但没回包说明是本机消防策略拦了如果一个包都没有那是网络路径或者上游安全组的问题。客户端指向内网服务器改/etc/chrony/chrony.confserver 192.168.1.10 iburst server 192.168.1.11 iburst内网时间服务器我建议至少两台互相之间形成备份关系客户端两条 server 都配上。这两台如果各自同步公网源那它们之间是独立的如果想做成一主一备的层级结构可以让备机把主机作为上游# 备机上配 server 192.168.1.10 iburst这种层级结构下主机挂了备机也能继续提供服务只不过 stratum 会加一级。层级不要太深一般三层以内太深了累计误差和收敛时间都会变大。另外别忘了chronyc在客户端上的验证chronyc sources -v chronyc tracking chronyc sourcestats -vchronyc sourcestats -v是第三个有用的命令它显示每个源的统计信息包括偏差估计、频率估计、残差等用来判断一个源的质量优劣。5.4 分层、冗余与监控一个规模稍大的内网时间架构可以这样分层出口同步一到两台公网源这两台作为第一层第一层下面挂几台内网时间服务器作为第二层普通业务机全部指向第二层。每一层内部做冗余客户端配置多个上游。监控方面有几件事要做。一是监控 chrony 服务本身systemctl is-active chronyd返回 active 才算正常。二是监控同步状态用chronyc tracking里的System time和Leap status做判断偏差超阈值就告警。三是监控上游可用性chronyc sources -v里如果^*那一行不见了说明当前没有可用源。写监控脚本的话chronyc支持交互式命令管道输入方便脚本解析chronyc -c tracking | awk -F, {print $4, $5}-c参数输出的是逗号分隔格式比解析人类可读的输出靠谱得多。同理chronyc -c sources也能拿到结构化数据。我这边的做法是在时间服务器上架一个轻量的监控探针每分钟采集一次tracking的关键字段推到时序库同时用sources的返回判断上游数量。业务机上则简单一些只采集是否同步这一个布尔值异常时告警。这样能快速定位是时间服务器出了问题还是某台业务机的配置出了问题。还有一个常被忽略的点网络摄像机和录像机这类终端设备。它们通常也支持 NTP 客户端但由于出厂默认配置的问题很多设备的时间是手动设的或者指向了一个早就不存在的地址。这类设备的时间戳在事后查证时非常关键建议纳入统一的 NTP 体系指向内网时间服务器格式是IP 地址 端口 123不需要认证。配置完之后一定进去看一眼实际生效的时间有些设备的配置界面上显示改了但需要重启才生效。6. 常见问题与排查实录6.1 同步不上的六步排查时间同步不上的原因分布很广我总结了一个固定的排查顺序从下往上走通常十分钟内能定位。第一步看服务在不在。systemctl status chronyd或systemd-timesyncd如果不是 running先看日志为什么起不来。常见原因是配置文件语法错误、端口被占、或者两个时间服务冲突。第二步看有没有源。chronyc sources -v如果输出里全是?说明所有源都不可达。如果输出里一行都没有说明配置文件里根本没配 server 或 pool。第三步看网络通不通。NTP 是 UDP用nc -u -z -w 2 服务器 123或者直接ntpdate -q 服务器做一次查询测试。注意ntpdate -q只查询不修改时间是安全的诊断手段。ntpdate -q ntp.aliyun.com输出的 offset 就是当前偏差。如果这条命令超时问题在网络层和 chrony 配置无关。第四步看防火墙。本机sudo iptables -L -n | grep 123、sudo ufw status云主机再看安全组。第五步看上游是否拒绝。有些公共时间源会对高频请求做限流或者要求客户端遵守某种规则。换一个源试试或者降低请求频率。第六步看系统时钟模式。RTC in local TZ如果是yes会引发一系列奇怪的问题用timedatectl set-local-rtc 0改掉。这个顺序的逻辑是从自身配置到网络再到对端因为绝大多数问题都出在前两步。6.2 虚拟机与 WSL 的时间漂移虚拟机里的时间问题是另一类症状和物理机不一样。VMware 虚拟机VMware Tools 默认会把宿主机时间同步给虚拟机。这个功能和 chrony 是冲突的两个东西都在调系统时钟结果就是时间来回跳。解决办法是关掉 VMware Tools 的时间同步sudo vmware-toolbox-cmd timesync disable然后在虚拟机设置里确认将客户机时间与主机同步这一项也关掉了。注意这个命令在某些版本的 open-vm-tools 里可能不存在那就直接改 vmx 配置文件加tools.syncTime FALSE。Hyper-V 虚拟机对应的选项叫时间同步在集成服务的设置里可以关掉。关掉之后让 chrony 独家负责时间。VirtualBox默认也提供时间同步服务可以在虚拟机的设置里关闭。WSL2这个比较特殊。WSL2 跑在一个轻量虚拟机里它的时钟依赖宿主 Windows 的时间但长时间运行之后会有明显漂移尤其是笔记本休眠唤醒之后。WSL2 里的 systemd 如果启用了chrony 可以正常跑但有些镜像里没有 systemd就得用别的方式。最直接的办法是重启 WSL# 在 Windows 的 PowerShell 里执行 wsl --shutdown重启之后时钟会重新跟随宿主。如果不想每次都重启可以在 WSL 里手动触发一次硬件时钟同步sudo hwclock -s这条命令在部分 WSL 版本上有效作用是强制从宿主的虚拟硬件时钟读一次时间。如果报错说没有权限加上 sudo 再试。作为一个长期方案我建议还是在 WSL 里配上 chrony 指向内网时间源或者干脆接受它会漂移这个事实把它当作开发机而不是生产机来用。6.3 排查速查表把前面零散的排查点整理成一张表出问题的时候可以直接对照。现象可能原因排查命令处理方式NTP service inactive服务未启动或被 masksystemctl status systemd-timesyncdenable --now后unmasksynchronized 一直是 no上游不可达或防火墙拦chronyc sources -v换源、开 123/udpsources 全是?网络不通、DNS 解析失败ntpdate -q 源检查 DNS 与路由Reach 为 0请求发出但无响应sudo tcpdump udp port 123查安全组和本机策略时间反复跳变两个同步服务在抢ps auxgrep -E chrony虚拟机时间总在漂宿主同步工具与 chrony 冲突查看 virtualbox/vmware 设置关闭宿主时间同步偏差大但慢慢磨makestep 策略太保守chronyc tracking手动chronyc makestep硬件时钟与 UTC 差 8 小时RTC 按本地时间存储timedatectl statusset-local-rtc 0容器内时间不对镜像设了 TZ 环境变量docker exec c date统一容器 TZ 为 UTC重启后精度恢复慢drift 文件被删ls /var/lib/chrony/不要删让其重新学习6.4 几条踩坑心得说几条我自己趟过的经验都是文档里不太会写的。第一改完配置一定用chronyd -Q做语法检查。有一次我手写配置少了个空格重启之后服务直接起不来而systemctl restart返回的是成功因为启动动作本身完成了直到我systemctl status才看到 failed。用-Q预检能省掉这类麻烦。第二客户端不要配太多源。我见过有人一口气配了八个 pool觉得源多了更可靠。实际上源太多会让 chrony 的选择算法有更多噪声输入收敛反而变慢。一般 3 到 4 个够了其中一个是备选。第三时间跳变引发的故障往往延迟暴露。曾经遇到一次虚拟机迁移之后时间回退了 20 分钟当时业务看着没事第二天早上定时任务全部乱套因为基于时间戳的任务调度被判成还没到时间。所以任何时间跳变操作之后都该手动检查一遍关键定时任务的状态。第四容器不要自己同步时间。Docker 容器和宿主共享内核时钟容器内改时间是不生效的除非加了 SYS_TIME 能力但那会影响到宿主。正确做法是保证宿主机时间准容器跟着走。Kubernetes 集群同理节点时间准了Pod 里的时间自然准。第五内网 NTP 服务器的可靠性比精度更重要。一台精度差 10 毫秒但常年稳定运行的时间服务器比一台精度 1 毫秒但每月挂一次的要好用得多。所以时间服务器要选长期开机、网络位置好的机器别用那种经常重启的机器。第六上线新机器时把时间检查写进初始化脚本。我现在用的做法是机器初始化脚本里加上时间同步的配置和验证最后跑一次timedatectl status把System clock synchronized: yes作为脚本的成功条件之一。这样新机器上线时就保证了时间正确不用等人发现问题再回头补。第七注意闰秒和时区数据更新。闰秒的处理由Leap status显示现代 chrony 和内核会平滑处理一般不用干预。时区数据tzdata则要随系统更新apt install tzdata升级时会提示选择时区选Asia/Shanghai就行。跨时区部署的系统要特别注意应用层的时间显示逻辑统一在展示层做转换存储层一律用 UTC。顺着这个思路往下走其实时间同步只是基础设施正确性里的一小块但它的特殊之处在于它几乎不被任何业务代码感知却是一切业务的时间基准。我个人的习惯是每接手一套新环境第一件事就是挨个timedatectl status看一遍。这个动作花不了两分钟但能挡掉后面大量的排查时间。要是你们环境里还有更刁钻的时间问题比如跨机房的时间一致、分布式事务里的时间戳冲突那些就属于更细的话题了得单独展开聊。