正常な証明書が拒否された
一言でいうと
証明書・トークン・キャッシュは、すべて「今何時か」を他者と合わせて見ます。自分の時計が間違っていれば、正常なものが拒否され、期限切れのものが通ります。
なぜ必要なのか
時計のずれの大半は、何の問題も起こしません。ログが数秒ずれるのは、読みにくいだけです。ところが、期限を判定する場所では、同じずれが、そのまま拒否になります。そして、その拒否は、原因を指しません。メッセージは「証明書はまだ有効ではありません」で、証明書は、受け取ったばかりの正常な証明書です。
特に人を混乱させるのが、自然に直るという点です。時計が12秒遅れているマシンは、新しい証明書を受け取ったあと、12秒間だけ失敗し、その後は問題ありません。デプロイ直後にだけエラーが数件出て、再現しません。このようなものは、たいてい「一時的なネットワークの問題」として片付けられ、次のデプロイでそのまま繰り返されます。
どう動くのか
X.509証明書には、有効期間が2つの時刻で入っています。notBeforeとnotAfterです。RFC 5280の4.1.2.5節は、この期間を「notBeforeからnotAfterまで、両端を含む」と定義しています。検証する側は、自分の時計で、今がその期間の中かどうかを見ます。そのため、同じ証明書が、あるマシンでは通り、あるマシンでは拒否されます。
openssl x509 -in edge-1.pem -noout -startdate -enddate
# notBefore=Nov 1 09:38:00 2025 GMT
# notAfter=Feb 1 09:38:00 2026 GMT
時計が遅れたマシンは、前側の端に引っかかります。新しく発行された証明書のnotBeforeが、そのマシンの「今」より未来なので、まだ有効ではないと判断します。引っかかる時間は、ちょうど遅れた分です。逆に、時計が進んでいるマシンは、後ろ側の端に引っかかります。他のマシンより先に、期限切れと見ます。2つの症状は反対方向で、どちらなのかがわかれば、時計がどちらにずれているかも、すぐにわかります。
トークンも、同じ構造です。RFC 7519のexpクレームは、「その時刻以降は(on or after)受け入れてはならない」で、nbfは、「その時刻より前は受け入れてはならない」です。そして、2つの節とも、同じ文を付け加えています。実装者は、時計のずれを考慮して、多少の余裕(leeway)を置いてもよく、普通は数分を超えません。
ここで、寿命とずれの関係が表に出ます。寿命300秒のトークンを、時計が277秒進んでいるマシンが検証すると、発行の23秒後から期限切れに見えます。ずれが寿命に近づくほど、使える時間が0に収束します。そのため、寿命が短いほど、時計の精度への要求が厳しくなります。1分のトークンを使うことにしたなら、時計のずれのバジェットを先に決める必要があります。
쓸 수 있는 시간 = 수명 - (검증하는 쪽 시계가 앞선 만큼)
수명 300초 · 오차 +277초 → 23초
수명 300초 · 오차 + 5초 → 295초
수명 60초 · 오차 +277초 → 아예 못 쓴다
このコードブロックの韓国語は、「使える時間 = 寿命 - (検証する側の時計が進んでいる分)」という式と、寿命・秒・ずれ・「まったく使えない」という意味です。
キャッシュも、同じ軸にあります。レスポンスに付く有効期限を絶対時刻で与えると、受け取る側の時計に左右され、残り秒で与えれば、受け取る側が自分の単調時計で数えるので、ずれの影響を受けません。HTTPがExpiresヘッダーとは別にCache-Control: max-ageを置き、後者を優先させた理由が、ここにあります。
現場での姿
自動更新が動く環境では、この問題がデプロイの周期とかみ合います。証明書を期限切れの直前に差し替える代わりに、余裕を大きく取って早めに差し替えれば、時計のずれが数分あっても、両端のどこにも引っかかりません。更新のタイミングを、期限の3分の2の地点あたりにする慣行が、ここから来ています。
認証失敗を調査するときの順序も、決まっています。メッセージが「not yet valid」か「expired」かを、まず見ます。前者なら、検証する側の時計が遅れているか、発行がちょうど終わったところで、後者なら、検証する側の時計が進んでいるか、本当に期限切れです。そのあと、両方のマシンの時刻を直接比べます。この2歩で、大半の事件が整理されますが、実際には、証明書を再発行するほうに、まず手が伸びます。そして、同じことが、次の更新のときにまた起きます。
余裕(leeway)を大きく取って覆い隠す方法もありますが、これには代償があります。期限切れのトークンを、その余裕の分だけ余計に受け入れるという意味だからです。再発行を取り消しとして使う設計なら、その時間の分だけ、取り消しが遅れて効きます。余裕は、時計を直すまでの暫定措置として置き、根本的な対策は、ずれを測って監視することです。
もう1つ。期限のせいで起きた失敗は、両端で症状が対称ではありません。前側の端の問題(まだ有効ではない)は、発行の直後に短く起きて自然に直り、後ろ側の端の問題(早期の期限切れ)は、一度始まると、時計を直すか証明書を更新するまで続きます。そのため、前者は「再現しない散発的なエラー」として、後者は「突然全部が死んだ」として、報告されます。同じ原因なのに、報告書のタイトルが正反対です。事件を受けたときに、この非対称性を先に思い浮かべれば、どちらの端を見るべきかが、すぐに決まります。
そして、期限を判定する場所は、コードの中で見つけやすいです。どこかで「今」を読み、他者が与えた時刻と比べる場所が、すべてその場所です。証明書の検証、トークンの検証、キャッシュの鮮度、署名付きURLの期限切れ、リプレイ攻撃を防ぐタイムスタンプのウィンドウが、すべて同じ形です。この場所の一覧を一度作っておけば、時計のずれのバジェットをいくらにすべきかが、推測ではなく計算になります。その一覧で最も短い寿命が、バジェットの上限を決めます。
次のクイズで確認すること
時計が遅れたマシンと進んだマシンが、それぞれ有効期間のどちらの端に引っかかるか、トークンの寿命と時計のずれがどのように掛け合わさって使える時間を減らすか、余裕を増やす対策の代償は何かを確認します。