VPSの時刻ずれを直す方法と原因
VPSの時刻ずれで2FAに失敗する原因を確認します。chronycとtimedatectlの出力から同期状態を調べ、UDP port 123やdaemon競合を修正する手順を解説します。
VPS の時刻がずれる理由
VPS の時刻がずれるのは、補正する仕組みが動作していないためです。kernel は、わずかに速く、または遅く動くハードウェアカウンターを基に時刻を数えます。時刻同期クライアントが動作していないと、この小さな誤差が時間とともに蓄積します。仮想マシンでは、もう1つ原因があります。guest は他の guest と物理 CPU を共有するため、スケジュールされていない間は時刻を数えられません。
現在の KVM guest では、カウンター自体が本当の問題であることはほとんどありません。準仮想化された kvm-clock source は host が管理する値を読み取るため、正常な guest は host の時刻を正確に追従します。目に見えてずれている clock は、通常、もっと単純な理由でずれています。同期 daemon が動作していない、2つの daemon が動作して競合している、または outbound UDP port 123 が provider の network 外へ出ていない、といった原因です。guest の時刻調整は host または NTP (network time protocol) から行われ、自身の oscillator に基づくものではありません。
実際に時刻がずれると何が壊れるか
- TOTP(time-based one-time password)の二要素認証コードが一致しなくなり、パスワードと cryptographic key がどちらも正しいサーバーから締め出されます。
- 1 分前に発行された証明書が拒否され、
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)によりリポジトリを拒否します。- スケジュールされた処理が誤った時刻に実行されます。時刻が飛ぶと、あるジョブが 2 回実行され、別のジョブがスキップされることもあります。
- 2 台のサーバーのログを時系列に並べられず、インシデントのタイムラインを推測で組み立てる必要があります。
許容範囲は、多くの人が想定するより狭いものです。以下の数値は、テストで測定した値ではなく、文書化されたデフォルト値です。
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 秒ごとに進むカウンターから計算されます。多くの検証システムは、前後 1 ステップまで受け入れます。つまり、各方向 30 秒のずれが許容範囲のすべてです。Kerberos はこれより大幅に寛容で、デフォルトのずれの許容値は 300 秒です。証明書には、このような余裕がまったくありません。固定された時刻と照合され、猶予期間は 0 秒です。そのため、時刻が 1 秒早いだけで、完全に有効な証明書が拒否されます。
3 つの時計と、重要なのはどれか
システムクロックが重要です。これはカーネルの CLOCK_REALTIME です。メモリ上で保持され、時刻を記録するすべての処理が読み取る、1970 年 1 月 1 日 00:00:00 UTC からの経過秒数です。ログ行、証明書の検証、TOTP コード、ファイルの更新時刻は、すべてこの時計を基にしています。「サーバーの時刻が間違っている」と言う場合は、通常この時計を指します。
ハードウェアクロックは RTC (real time clock) とも呼ばれ、マシンの電源が切れている間も動作する独立したカウンターです。物理マシンでは、バッテリーで保持されるチップです。ゲスト内ではハイパーバイザーがエミュレートするため、実質的にはホスト側の情報です。Linux は起動時に開始値を取得するために 1 回だけ読み取り、その後は独自のカウントを維持します。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_clocksourceKVM では通常、kvm-clock と表示されます。これはホストが管理する値を読み取ります。そのため、NTP クライアントをまったく実行していない KVM ゲストでも、しばらくの間はおおむね正しい時刻を維持できます。tsc は CPU 自身のカウンターです。Xen ゲストでは xen、Hyper-V ゲストでは hyperv ソースが報告されます。測定結果に基づく変更理由がない限り、この設定は変更しないでください。カーネルは、そのハードウェア上で信頼できる最適なソースをすでに選択するためです。
一部のホストでは、PTP (precision time protocol) デバイスをゲストに割り当てることもできます。これにより、chrony はネットワーク経由ではなく、ホストのクロックを直接読み取れます。確認する価値はありますが、共有 VPS では利用できないことがよくあります。
sudo modprobe ptp_kvm
ls /dev/ptp*
cat /sys/class/ptp/ptp0/clock_namemodprobe が失敗するか、デバイスが表示されない場合は、ホストがその機能を提供していません。その場合は、ネットワーク経由の NTP を使用します。clock_name が KVM 仮想クロックを示す場合、chrony の設定に refclock PHC /dev/ptp0 poll 2 行を追加して使用できます。
自分のマシンの時刻状態を確認する
まず、1 つのコマンドを実行します。これだけで、「このクロックを正確に保っているものがあるか」という問いに、1 画面で答えられます。
timedatectl覚えている数値を信じるのではなく、次の行を確認します。
Local timeとUniversal timeは、同じ時点をローカルのタイムゾーンと UTC で表示したものです。両者が同じなら、マシンはすでに UTC で動作しています。RTC timeは前述のハードウェアクロックです。VPS では無視してください。Time zoneは、システムがローカル時刻の表示に使用する設定です。System clock synchronizedは、カーネル独自のフラグです。時刻デーモンは、時刻ソースを信頼できると判断した時点でこのフラグを設定します。そのため、noはブート後にこのクロックへ時刻調整が行われていないことを示します。NTP serviceは systemd-timesyncd に限定した状態です。timesyncd がインストールされていない chrony 稼働中のマシンでは、n/aが正常です。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 がクロックについて測定したレート誤差で、すでに補正に使用されています。Leap status は Normal になるはずです。Not synchronised で、Reference ID が 00000000 () の場合、chrony はまだ時刻ソースを確定できていません。
chronyc sources -v は一覧の上に凡例を表示するため、記号を覚える必要がありません。意味の大部分は 2 つの列で判断できます。各行の先頭にある状態文字は、chrony がそのソースをどう評価しているかを示します。* は現在使用中のソースを示し、すべての行に ? が表示される場合は、応答しているソースがありません。Reach は、直近 8 回のポーリングに対する応答履歴を 8 進数で表示します。377 は 8 回すべて応答があり、0 は 1 回も応答がないことを示します。
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(simple network time protocol)クライアントです。一度に 1 台のサーバーへ問い合わせ、そのサーバーに合わせて時刻を少しずつ補正します。常時オンラインで、起動時の時刻が大きくずれていないマシンであれば、これで十分です。実行に必要なリソースもほとんどありません。
chrony は完全な NTP 実装です。仮想マシンでは、独自の出力からも分かる理由により、こちらをデフォルトにする方が適しています。複数の時刻ソースを同時にポーリングし、互いに一致しないソースを破棄します。さらに、クロックの周波数誤差を測定して drift file に書き込むため、各サンプルを追いかけるのではなく、クロックが持つずれの傾向を補正します。また、物理サーバーでは発生しない、VM 特有の次の 2 つの事象からも素早く復旧できます。ホストによって一時停止されることと、実行中に別のホストへ移動されることです。ホストの 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 を削除します。これは正しい動作であり、意図されたものです。両方を実行しないでください。同じクロックを設定する 2 つのデーモンが競合し、実行中はどちらが報告する offset も信頼できなくなります。Rocky と AlmaLinux では sudo dnf install -y chrony でインストールします。この場合、unit 名は chrony ではなく chronyd です。
設定ファイルは、Debian と Ubuntu では /etc/chrony/chrony.conf、Rocky と Alma では /etc/chrony.conf です。ディストリビューションのデフォルト設定は VPS に適した内容になっているため、理由がある場合だけ変更してください。理解しておく価値のあるディレクティブは 2 つあります。
poolとserverの行で時刻ソースを指定します。iburstを追加すると、起動時に chrony が高速なバーストを送信します。そのため、最初の同期が数分ではなく数秒で完了します。makestepは、chrony が時刻を徐々に補正するのではなく、段階的に変更するタイミングを決めます。grep -n makestep /etc/chrony/chrony.confで現在の設定を確認してください。Debian と Ubuntu のデフォルトであるmakestep 1 3は、次の意味です。chronyd の起動後 3 回目までの更新では、時刻が 1 秒を超えてずれていれば段階的に補正し、それ以降は徐々に補正します。
経路上での改ざんに対して時刻通信を認証したい場合、chrony 4 以降では NTS(network time security)を使用できます。まず chronyd -v でバージョンを確認してください。また、NTS には UDP 123 に加えて、送信方向の TCP 4460 も開放されている必要があります。
server time.cloudflare.com iburst nts信頼する前に再起動して検証してください。設定の解析に失敗すると、時刻デーモンがまったく動作しない状態になります。そのことをクロック自体から知ることはできません。
sudo systemctl restart chrony
chronyc sources -v
chronyc tracking数分ずれた時計がずれたままになる理由
時刻デーモンがオフセットを修正する方法は2つあります。slew では、誤差がなくなるまで時計を速めるか遅くします。これにより時刻は常に前進し、同じタイムスタンプが繰り返されたり、タイムスタンプが飛んだりすることはありません。step では、正しい値へ一気に変更します。処理は速い一方で、時計が後戻りする可能性があります。wall clock からの経過時間を測定するソフトウェアにとって、時計の後戻りは危険です。そのため、どちらのデーモンも slew を優先します。
この優先動作により、大きくずれた時計が長時間ずれたままになることがあります。chrony が step を実行するのは、makestep が許可するウィンドウ内に限られます。デフォルトでは、デーモンの起動後に行われる最初の数回の更新時だけです。1週間稼働していた chronyd が40秒の誤差を検出した場合、その誤差は slew で修正されます。しかし、40秒の slew には待ちたくないほど長い時間がかかります。静かなタイミングを選び、意図的に1度だけ強制します。
sudo chronyc makestep
chronyc trackingchronyc tracking は、System time オフセットがゼロに近い値になったことを報告するはずです。また、Last offset には、直前に修正した量が表示されます。負荷の高いデータベースホストで実行する前に、よく検討してください。時計が後戻りすると、時刻は常に前進すると想定しているソフトウェアが混乱する可能性があります。デーモンを再起動する方法は、同じ修正をより穏やかに行う手段です。再起動時には makestep ウィンドウが再び開くためです。
コンテナはホストのクロックを共有します
コンテナには独自の実時間クロックがないため、コンテナ内で同期する対象はありません。Linux の time namespace が仮想化するのは、単調増加クロックと起動時刻クロックだけです。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 に実行する日次ジョブは、時計を戻す日に 2 回実行され、時計を進める日にはまったく実行されません。man 8 cron には、3 時間未満の時刻変更に対する特別な処理が記載されています。時計を進めたことでスキップされたジョブは変更直後に実行され、時計を戻したことで重複した時間帯に該当したジョブは 2 回目には実行されません。この動作は妥当ですが、03:00 にその動作を考慮する必要はありません。UTC では、ジョブは年間を通じて毎日 1 回実行されます。ジョブが奇妙な時刻に実行されるのではなく完全に実行されない場合は、cron ジョブが何も記録せずに実行されない理由のほうが可能性の高い説明です。
ログの読み取りにも同じことが当てはまります。journalctl はシステムのタイムゾーンでタイムスタンプを表示し、journalctl --utc は UTC を強制します。異なるタイムゾーンにある 2 台のサーバーでは、インシデントのたびに時刻変換が必要になります。緊迫した状況で変換すると、タイムラインを読み違えやすくなります。システムは UTC に設定し、タイムスタンプは UTC で保存し、人が読む時点で 1 回だけ変換します。1 つのコマンドだけ現地時刻で表示したい場合は、マシンの設定を変更せずに指定できます。
TZ=Europe/Berlin datetimedatectl の出力には、このセクションに関係する行がもう 1 つあります。RTC in local TZ は no になっている必要があります。これを yes に設定するのは、ノート PC で Windows とデュアルブートする場合の回避策です。サーバーでは、後で誰かが混乱する原因となるオフセットを追加するだけです。設定されている場合、timedatectl は、システムが RTC の時刻を現地タイムゾーンで読み取る設定になっているという警告を表示します。
症状別のトラブルシューティング
1 台のサーバーで二要素認証コードが拒否される。 ほかのことを確認する前に、まず時刻を確認します。コードは 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 port 123 を使用します。ネットワークによっては、この通信をフィルタリングまたはリダイレクトします。sudo chronyc ntpdata は Total TX や Total RX を含むソースごとのカウンターを表示します。RX が 0 のまま TX だけが増える場合、パケットは送信されていますが応答がありません。自分の環境とソースの間にあるファイアウォールが原因である可能性があります。
時刻は正しかったが、その後に急にずれた。 ホスト側のイベントが原因になることがあります。復元したスナップショット、一時停止したゲスト、または別のホストへのライブマイグレーションによって、ゲストが認識する時刻が実際の時刻より遅れることがあります。chrony は次回のポーリングで検出して修正します。systemd-timesyncd は、最初に長いポーリング間隔が経過するまで待つことがあります。systemctl is-enabled chrony でデーモンがブート時に起動することを確認します。手動で起動したデーモンは、次回の再起動後には終了しているためです。
オフセットは小さいが、いつまでも収束しない。 CPU steal を確認します。タイマー割り込みの予定時刻にゲストがスケジュールされていないと、サンプルの取得が遅れます。そのため、オフセットは収束せずに変動します。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 が状態を報告する構成を説明しています。このようなチェックはデーモンではなく短いスクリプトなので、デフォルトではなく Type=oneshot を使用します。systemd のサービス種別の概要では、誤った種別を指定すると、実際には成功していない処理を成功と報告する unit になる理由を説明しています。
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 は 1 台のサーバーに追従する SNTP クライアントで、常時オンラインで起動時の時刻が大きくずれていないマシンには適しています。chrony は複数の時刻ソースをポーリングし、値が一致しないソースを除外して、時計の進み方の誤差を学習します。また、ホストの一時停止やライブマイグレーションの後もすばやく復旧します。Debian または Ubuntu に chrony をインストールすると、systemd-timesyncd は自動的に削除されます。両方のパッケージが time-daemon を提供するためです。2 つの時刻デーモンを同時に実行しないでください。
1 台のサーバーでは TOTP コードが失敗するのに、他では機能するのはなぜですか?
TOTP コードは現在時刻の関数だからです。コードは 30 秒ごとに進むカウンターから生成されるため、サーバーとスマートフォンが同じステップを参照している必要があります。ほとんどの検証処理は前後 1 ステップを許容するため、各方向におよそ 30 秒の余裕があります。そのサーバーで timedatectl を確認します。System clock synchronized が no と表示される場合は、時刻同期を修正してください。共有 Secret を変更しなくても、コードが再び一致します。
Docker コンテナ内で時刻を設定できますか?
いいえ。その必要もありません。コンテナはホストの CLOCK_REALTIME を共有します。Linux の time namespace が仮想化するのは monotonic clock と boot-time clock だけだからです。権限のないコンテナには date: cannot set date: Operation not permitted が付与されます。CAP_SYS_TIME を追加すると、コンテナ固有の時計ではなくホストの時計を変更できるようになります。ホスト側で時刻を同期してください。コンテナ内だけ異なる現地時刻を使用する場合は、タイムゾーンの設定です。コンテナの環境で TZ を設定します。
サーバーでは UTC と現地時刻のどちらを使用すべきですか?
UTC を使用し、人が出力を読む時点で現地時刻に変換します。UTC は夏時間によって変化しないため、日次ジョブは年間を通じて 1 日に 1 回実行され、異なるサーバーのタイムスタンプも変換なしで揃います。sudo timedatectl set-timezone UTC で設定します。現地時刻で確認したい場合は、TZ=America/New_York date のように単一のコマンドの前に指定できます。これはシステム時計を変更しません。