VPS 時鐘漂移怎麼辦?檢查與修復時間同步
VPS 時鐘漂移會讓 TOTP 登入失敗。了解 3 個時鐘的差異,使用 chronyc 與 timedatectl 找出同步問題,修復 UDP port 123 或衝突的 daemon。
VPS 時鐘為何會漂移
VPS 時鐘會漂移,是因為沒有任何機制校正時間。核心會根據硬體計數器計時,而該計數器可能略快或略慢。若沒有執行時間同步用戶端,這項微小誤差會逐小時累積。在虛擬機器中,還有另一項原因:來賓系統與其他來賓系統共用實體 CPU,因此未獲排程的期間也無法計時。
在目前的 KVM 來賓系統中,計數器本身通常不是實際問題。半虛擬化 kvm-clock 來源會讀取由主機維護的數值,因此健康的來賓系統能緊密追隨主機。時鐘明顯錯誤,通常是更單純的原因所致。可能是沒有執行同步 daemon,也可能是同時執行兩個 daemon 而互相衝突,或是對外的 UDP port 123 根本無法離開供應商的網路。來賓系統會依靠主機或 NTP(network time protocol)校準時間,而不是依靠自己的振盪器。
錯誤的時鐘實際上會造成什麼問題
- 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 秒的寬限期,因此時鐘快一秒,就會拒絕完全有效的憑證。
3 個時鐘,以及哪一個才重要
系統時鐘才是重要的時鐘。它是核心的 CLOCK_REALTIME:自 1970 年 1 月 1 日 UTC 起算的秒數,儲存在記憶體中,所有需要加上時間戳記的元件都會讀取它。日誌行、憑證檢查、TOTP code 和檔案修改時間都來自系統時鐘。有人說伺服器的時間不正確時,指的就是這個時鐘。
硬體時鐘也稱為 RTC(real time clock),是獨立的計數器,即使機器關機仍會持續運作。在實體機器上,它是由電池供電的晶片。在 guest 內,它由 hypervisor 模擬,因此主要反映 host 的狀態。Linux 會在開機時讀取一次硬體時鐘,取得初始值,之後則維持自己的計數。timedatectl 會在 RTC time 行顯示它。在 VPS 上不要根據這一行進行除錯,因為它反映的是 host 對時間的判定,而不是系統時鐘的同步狀態。在 container 中通常根本沒有 /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 client,仍能在一段時間內大致維持正確。tsc 是 CPU 自己的計數器。Xen guest 會回報 xen,Hyper-V guest 則會回報 hyperv 來源。除非已測得明確的變更理由,否則不要修改這項設定,因為核心已會在該硬體上選擇它信任的最佳來源。
有些 host 也會將 PTP(precision time protocol)裝置提供給 guest,讓 chrony 直接讀取 host 時鐘,而不必透過網路。這項功能值得檢查,但共用 VPS 通常無法使用:
sudo modprobe ptp_kvm
ls /dev/ptp*
cat /sys/class/ptp/ptp0/clock_name如果 modprobe 失敗,或沒有顯示任何裝置,表示 host 未提供這項功能,此時只能使用網路 NTP。如果 clock_name 指出 KVM virtual clock,chrony 就能在設定檔中加入 refclock PHC /dev/ptp0 poll 2 行來使用它。
在本機讀取時間狀態
先執行一個命令。它會在單一畫面中回答「目前是否有任何服務在維持這個時鐘的正確時間」。
timedatectl請閱讀這些行,不要只相信自己記得的某個數字:
Local time與Universal time是同一個時間點,分別以本機時區和 UTC 顯示。如果兩者相同,表示機器已使用 UTC。RTC time是前述的硬體時鐘。在 VPS 上請忽略它。Time zone是系統用來格式化本機時間的設定。System clock synchronized是 kernel 自己的旗標。時間 daemon 信任其時間來源後會設定此旗標,因此no表示自開機後沒有任何服務校正過這個時鐘。NTP service專門回報 systemd-timesyncd 的狀態。在執行 chrony 的機器上,n/a屬於正常狀態,因為該機器未安裝 timesyncd。System clock synchronized: yes搭配NTP service: n/a表示由 chrony 負責校時,且 kernel 也確認此狀態。
接著確認時間偏差多少。不要拿手機目測比較。如果 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 是最近 8 次輪詢的回應歷史,以八進位列印:377 表示 8 次全部收到回應,0 表示一次都沒有收到回應。
如果由 systemd-timesyncd 負責校時:
timedatectl timesync-status
timedatectl show-timesync --alltimesync-status 會列印正在連線的伺服器、輪詢間隔和 Offset 值。如果命令回傳的是服務錯誤,而不是列印狀態,表示這台機器不是由 timesyncd 這個 daemon 負責校時;這本身就是問題的答案。
若不使用額外工具,要粗略與外部世界比較,可以將本機時鐘與公開 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 實作,對虛擬機而言是較好的預設選擇,原因可從其輸出中看出。它會同時輪詢多個來源,並捨棄彼此不一致的來源。它會測量系統時鐘的速率誤差,並將結果寫入 drift file,因此能修正時鐘本身的偏差,而不是逐一追蹤每個取樣值。它也能快速處理虛擬機特有、實體主機不會遇到的兩種情況:虛擬機可能被主機暫停,也可能在執行期間遷移到另一台主機。如果主機提供 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。這是正確且預期的行為。不要同時執行兩者,因為兩個 daemon 會同時設定相同的系統時鐘;在此情況下,任何一方回報的偏移量都不可信。在 Rocky 和 AlmaLinux 上,請使用 sudo dnf install -y chrony 安裝;其中的 unit 名稱是 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 port 4460:
server time.cloudflare.com iburst nts重新啟動後先驗證,再信任其結果。設定檔若解析失敗,系統將完全沒有時間 daemon 執行,而系統時鐘也不會告知你發生了這個問題。
sudo systemctl restart chrony
chronyc sources -v
chronyc tracking為何偏差數分鐘的時鐘會持續錯下去
時間 daemon 有兩種修正偏差的方法。Slewing 會加快或減慢時鐘,直到消除誤差。這會讓時間持續向前推進,不會重複或跳過時間戳記。Stepping 會直接跳到正確的時間。這種方式速度快,但可能讓時鐘倒退。對於以 wall clock 測量經過時間的程式而言,時間倒退很危險。因此,兩種 daemon 都偏好 slewing。
這項偏好也是嚴重偏差的時鐘可能長時間維持錯誤的原因。chrony 只會在 makestep 允許的時間範圍內執行 stepping;預設情況下,這是 daemon 啟動後的前幾次更新。chronyd 執行一週後才發現偏差四十秒時,會改用 slewing,而修正四十秒所需的時間遠超過一般可接受的等待時間。請在服務負載較低的時段,刻意強制修正一次:
sudo chronyc makestep
chronyc tracking此時 chronyc tracking 應回報接近零的 System time 偏差,而 Last offset 應顯示剛才修正的幅度。在忙碌的資料庫主機上執行前請先評估,因為時鐘倒退可能使假設時間只會向前推進的軟體產生混亂。重新啟動 daemon 是相同修正方式中較溫和的做法,因為啟動時 makestep 時間範圍會再次開啟。
容器共用主機的時鐘
容器沒有自己的實際時鐘,因此無須在容器內進行同步。Linux time namespace 只會虛擬化單調時鐘與開機時間時鐘。CLOCK_REALTIME 不會虛擬化,因此容器讀取的系統時鐘與其執行所在的主機相同。在主機上修正時鐘後,該主機上的所有容器也會在同一時間完成修正。
這會導致幾項結果。請勿在 image 中安裝 chrony 或 ntpd,因為最好的情況也只是沒有作用。在無特權容器內設定日期會因 date: cannot set date: Operation not permitted 而失敗,因為核心要求該呼叫具備 CAP_SYS_TIME。授予 CAP_SYS_TIME 不會讓容器取得私有時鐘,而是讓容器能夠變更主機的時鐘,因此也會變更其他所有容器的時鐘。
容器使用不同時區不是時鐘問題。內含自身 /etc/localtime 的 image 會將同一個時間點依另一個時區格式化,因此 date 看似錯誤,但時鐘其實正確。在容器環境中設定 TZ=UTC 即可消除混淆。選用的 runtime 不會改變這一點,而rootless Podman 與 Docker 的比較會說明它實際改變的內容。
時區:伺服器使用 UTC,人員使用當地時間
將機器設定為 UTC,並維持不變。
timedatectl list-timezones | grep -i utc
sudo timedatectl set-timezone UTC
timedatectlUTC 沒有日光節約時間,這就是全部理由。在採用日光節約時間的時區中,設定於 02:30 執行的每日工作,會在時鐘回撥的當天執行 2 次,並在時鐘前撥的當天完全不執行。man 8 cron 說明了少於 3 小時的時間調整所採用的特殊處理方式:因時間向前跳而略過的工作,會在變更後不久執行;因時間向後跳而落在重複時段內的工作,不會再次執行。這種行為是合理的,但您不應該在 03:00 時還必須推演這些規則。使用 UTC 時,工作每天執行 1 次,全年皆然。如果您的工作是完全遺失,而不是在異常時間執行,則較可能的原因是cron 工作靜默地從未執行的原因。
讀取日誌時也適用相同的理由。journalctl 會使用系統時區格式化時間戳記,journalctl --utc 則強制使用 UTC。兩台伺服器位於不同時區時,每次事件處理都會變成時間換算工作,而人在壓力下進行換算,很容易誤讀時間軸。讓系統使用 UTC,以 UTC 儲存時間戳記,並在人員讀取時一次完成轉換。若有人只想讓單一命令顯示當地時間,不必變更機器設定即可做到:
TZ=Europe/Berlin datetimedatectl 的輸出中還有 1 行屬於本節。RTC in local TZ 應顯示 no。將其設為 yes 是為了在筆記型電腦上與 Windows 雙重開機時的權宜作法;在伺服器上,這只會加入一個日後可能造成混淆的偏移量。設定此值後,timedatectl 會顯示警告,指出系統已設定為使用當地時區讀取 RTC 時間。
依症狀進行疑難排解
雙因素驗證代碼在某一台伺服器上遭拒。 先檢查時鐘,再檢查其他項目。代碼來自每 30 秒遞增一次的計數器,因此伺服器若慢了 90 秒,計算出的代碼會對應到手機已經通過的時間步進。timedatectl 會顯示 System clock synchronized: no,或 chronyc tracking 會回報很大的 System time 偏移量。這與金鑰直接遭拒不同;後者會顯示自己的訊息,詳見公鑰驗證失敗指南。
apt update 表示 Release 檔案目前尚未生效。 完整訊息會列出 repository,以及該檔案還要多久才會有效,例如 is not valid yet (invalid for another 1d 2h 3min 4s)。你的時鐘落後於 repository 的 Release 檔案內所記載的日期,而該時間長度就是落後幅度的直接測量值。修正時鐘。不要停用 apt 的日期檢查來繞過問題,因為正是這項檢查防止他人提供過期的套件索引。
每個來源列都顯示無法連線狀態,且 Reach 為 0。 沒有任何來源回應,因此應檢查對外流量,而不是先檢查設定。NTP 對外使用 UDP port 123,部分網路會過濾或重新導向該流量。sudo chronyc ntpdata 會列印每個來源的計數器,包括 Total TX 與 Total RX。TX 計數持續增加,但 RX 維持 0,表示封包已離開本機,卻沒有任何封包返回,這通常指向你與來源之間的防火牆。
時鐘原本正確,之後突然跳動。 主機事件可能造成這種情況。還原 snapshot、暫停 guest,或 live migration 到另一台主機,都可能使 guest 的時間落後於實際時間。chrony 會在下一次輪詢時察覺並修正;systemd-timesyncd 可能會先等待較長的輪詢間隔。使用 systemctl is-enabled chrony 確認 daemon 會在開機時啟動,因為手動啟動的 daemon 會在下次重新開機後消失。
偏移量很小,但始終無法穩定。 檢查 CPU steal。guest 在計時器中斷到期時未獲得排程,取樣就會延遲,因此偏移量會持續變動,而不是逐漸收斂。top 會在 CPU 列中以 st 數值顯示此情況。在共用主機上讀取 CPU steal time 說明該數值的意義,以及可採取的處理方式。
剛簽發的憑證遭拒,因為尚未生效。 curl 會列印 SSL certificate problem: certificate is not yet valid,瀏覽器也會顯示類似訊息。憑證本身沒有問題;檢查憑證的時鐘落後了。問題可能出在用戶端或伺服器,因此兩端都要檢查。如果簽發憑證的伺服器時鐘不正確,certbot 與 nginx 憑證指南 說明同一套設定中的續期處理方式。
將它加入現有的檢查流程
時間同步是開機時的設定,可能在數個月後無聲失效。這正是例行檢查能捕捉、記憶卻無法可靠追蹤的問題。timedatectl 和 chronyc tracking 一起查看只需 2 秒。將它們納入新 VPS 啟用後前 10 分鐘的檢查,並在執行例行 Linux 伺服器維護檢查清單時再次確認。如果希望自動執行檢查,並在偏移量變大時發出通知,撰寫 systemd 服務與計時器說明如何建立依排程回報的小型 unit。
FAQ
如何確認 VPS 的時鐘是否同步?
執行 timedatectl,並讀取 System clock synchronized 那一行。這是 kernel 自己的旗標,由負責校正時鐘的 daemon 設定,因此在使用 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 client,適合持續上線且初始時間已接近正確的主機。chrony 會輪詢多個來源,排除彼此不一致的來源,學習時鐘的速率誤差,並在主機暫停或 live migration 後快速恢復。由於兩個套件都提供 time-daemon,因此在 Debian 或 Ubuntu 上安裝 chrony 會自動移除 systemd-timesyncd。不要同時執行兩個時間 daemon。
為什麼我的 TOTP 驗證碼在一台伺服器上失敗,在其他地方卻正常?
因為 TOTP 驗證碼是目前時間的函數。驗證碼來自每 30 秒遞增一次的計數器,因此伺服器與手機必須判定目前處於同一個時間步進。多數驗證器會接受前後各一個時間步進,因此前後各約有半分鐘的誤差容許範圍。請在該伺服器上檢查 timedatectl。如果 System clock synchronized 顯示 no,請修正時間同步;驗證碼就會再次相符,無須變更 shared secret。
可以在 Docker container 內設定時間嗎?
不行,也沒有必要。container 會共用主機的 CLOCK_REALTIME,因為 Linux time namespace 只會虛擬化 monotonic clock 與 boot-time clock。未具備特權的 container 會取得 date: cannot set date: Operation not permitted;加入 CAP_SYS_TIME 後,它可以變更主機的時鐘,而不是取得自己的時鐘。請改為同步主機。container 內不同的當地時間屬於時區設定,因此請在 container 環境中設定 TZ。
伺服器應使用 UTC 還是當地時間?
使用 UTC,並在人員讀取輸出時套用當地時間。UTC 不會因日光節約時間而變動,因此每日工作全年每天只會執行一次,不同伺服器的時間戳記也能直接對齊,無須轉換。使用 sudo timedatectl set-timezone UTC 設定。若需要查看當地時間,可以在單一命令前加上前綴,例如 TZ=America/New_York date;這不會變更系統時鐘。