証明書の有効期限切れ(Certificate Expiry)
概要
証明書の有効期限切れ(Certificate Expiry) は、SSL/TLS 証明書の NotAfter フィールドに記録された日時を過ぎた状態です。RFC 5280 Section 4.1.2.5 で定義された X.509 証明書の Validity フィールドには NotBefore と NotAfter の 2 つの日時が含まれ、現在時刻がこの範囲外であれば証明書は無効と判定されます。
期限切れの証明書を返すサーバーに対して、ブラウザーは HTTPS 接続をブロックしセキュリティ警告を表示します。Chrome は NET::ERR_CERT_DATE_INVALID、Firefox は SEC_ERROR_EXPIRED_CERTIFICATE というエラーコードを表示し、ユーザーは通常のクリック操作ではページを閲覧できません。API クライアントやモバイルアプリも TLS ハンドシェイクに失敗し、サービスが全面停止する原因になります。
証明書の期限切れは、障害の原因として単純でありながら頻度が高い問題です。大手企業でも繰り返し発生しており、2020 年 2 月には Microsoft Teams が証明書の期限切れで数時間のサービス停止を経験しています。
仕組み
X.509 証明書の有効期間フィールド、ブラウザーのエラー表示、有効期間の上限規定を説明します。
有効期間の構造
X.509 証明書の Validity フィールドは ASN.1 の SEQUENCE として定義され、NotBefore と NotAfter の 2 つの時刻を含みます。時刻は UTCTime(2049 年まで)または GeneralizedTime(2050 年以降)でエンコードされます。
Validity ::= SEQUENCE {
notBefore Time,
notAfter Time
}
TLS ハンドシェイク中にクライアントが証明書を受け取ると、自身のシステム時刻と NotBefore / NotAfter を比較します。現在時刻が NotBefore より前であれば「まだ有効でない」、NotAfter を過ぎていれば「期限切れ」として接続を拒否します。
ブラウザーごとのエラー表示
各ブラウザーは証明書の期限切れを検出すると、全画面の警告ページを表示します。
Chrome は「この接続ではプライバシーが保護されません」という見出しとともに NET::ERR_CERT_DATE_INVALID を表示します。「詳細設定」ボタンを押すと接続を強行するリンクが現れますが、一般ユーザーの大半はここで離脱します。
Firefox は「警告: 潜在的なセキュリティリスクあり」と表示し、SEC_ERROR_EXPIRED_CERTIFICATE というエラーコードを示します。Firefox も「危険性を承知で続行」の選択肢を提供しますが、複数の手順を踏む必要があり、意図的に設計されたハードルです。
Edge は Chrome と同じ Chromium ベースのエラー画面を使い、証明書の期限が何日前に切れたかを表示する場合があります。
有効期間の上限
2020 年 9 月 1 日以降、CA/Browser Forum のベースライン要件により、新規発行される証明書の最大有効期間は398 日です。Apple が Safari で先行して施行し、Google と Mozilla が追随しました。
2025 年 4 月 11 日、CA/Browser Forum は Ballot SC-081v3 を可決し、段階的な有効期間短縮が決定しました。Apple、Google、Mozilla、Microsoft の 4 ブラウザーベンダーが賛成し、25 の CA が賛成票を投じています。
- 2026 年 3 月 15 日: 最大 200 日
- 2027 年 3 月 15 日: 最大 100 日
- 2029 年 3 月 15 日: 最大 47 日
有効期間が短くなるほど、自動更新の失敗が即座に障害につながります。手動更新で運用していた組織は、47 日サイクルへの移行前に自動化を整備する必要があります。
Let’s Encrypt の短命化
Let’s Encrypt は業界標準に先行して証明書の短命化を進めています。現在の 90 日有効から、2027 年 2 月に 64 日、2028 年 2 月に 45 日へ段階的に短縮する計画を 2025 年 12 月に発表しました。CA/Browser Forum が定めた 47 日という最終期限より 1 年早い実装です。
設定例
certbot と acme.sh を使った証明書の自動更新設定を示します。
certbot による自動更新
Let’s Encrypt の証明書を自動更新する基本設定です。
# 自動更新のテスト(実際には更新しない)
certbot renew --dry-run
# 更新後に nginx をリロードするフック付き
certbot renew --deploy-hook "systemctl reload nginx"
certbot は systemd タイマーまたは cron で 1 日 2 回実行されます。有効期限の残り 30 日を切った証明書を自動的に更新します。
# systemd タイマーの状態を確認
systemctl status certbot.timer
# 次回の更新タイミングを確認
systemctl list-timers certbot
deploy-hook の設定
certbot が証明書を更新しても、Web サーバーが新しい証明書を読み込まなければ意味がありません。更新後のリロードを確実にするため、renewal 設定ファイルにフックを記述します。
# /etc/letsencrypt/renewal/example.com.conf
[renewalparams]
deploy_hook = systemctl reload nginx
acme.sh による自動更新
# 証明書の取得と nginx リロードの設定
acme.sh --issue -d example.com -w /var/www/html \
--reloadcmd "systemctl reload nginx"
# cron ジョブの確認(acme.sh がインストール時に自動登録)
crontab -l | grep acme
確認方法
openssl で証明書の有効期限を確認するには次のコマンドを使います。
# 有効期限の確認(NotBefore と NotAfter を表示)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
出力例です。
notBefore=Jan 15 00:00:00 2026 GMT
notAfter=Apr 15 23:59:59 2026 GMT
残り日数を計算するスクリプトも役立ちます。
# 有効期限までの残り日数を表示
expiry=$(openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 )) days remaining
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から見た結果を取得できます。
curl "https://labee.dev/api/ssl-cert?hostname=example.com"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
data.reachable が true であれば TLS ハンドシェイクが成功しており、証明書は有効です。false が返る場合、error フィールドに接続失敗の原因が含まれます。証明書の期限切れ、ポートのファイアウォール、DNS 解決の失敗が主な原因です。
よくある問題
証明書の期限切れに関連する運用トラブルと対処法を整理します。
自動更新は成功したが Web サーバーに反映されない
certbot が証明書ファイルを更新しても、nginx や Apache がプロセスを再起動またはリロードしなければ、メモリ上の古い証明書を返し続けます。certbot のログ(/var/log/letsencrypt/letsencrypt.log)で更新成功を確認した後、openssl s_client で実際に配信されている証明書の NotAfter を確認します。差異がある場合は deploy-hook が設定されていないか、リロードコマンドが失敗しています。
ワイルドカード証明書の DNS-01 認証失敗
ワイルドカード証明書(*.example.com)は HTTP-01 チャレンジでは取得できず、DNS-01 チャレンジが必要です。DNS プロバイダーの API トークンが期限切れになると、certbot が _acme-challenge TXT レコードを作成できず更新に失敗します。API トークンの有効期限も証明書と同様に管理対象です。
複数サーバーへの証明書配布
ロードバランサーの背後に複数の Web サーバーがある環境では、certbot が 1 台で更新した証明書を他のサーバーに配布する仕組みが必要です。配布スクリプトが 1 台でも失敗すると、ラウンドロビンで期限切れの証明書を返すサーバーに当たり、断続的なエラーが発生します。外部からの監視では再現性が低く、原因特定に時間がかかります。
クライアント側のシステム時刻のずれ
ユーザーの端末のシステム時刻が大幅にずれている場合も ERR_CERT_DATE_INVALID が発生します。サーバー側の証明書に問題がないにも関わらずエラー報告を受けた場合は、クライアントの時刻設定を確認します。仮想マシンやコンテナ環境では、ホスト OS との時刻同期が切れているケースがあります。
中間証明書の期限切れ
サーバー証明書の有効期限だけを監視していると、中間証明書の期限切れを見落とします。中間証明書が期限切れになると証明書チェーンの検証が失敗し、サーバー証明書自体が有効であっても NET::ERR_CERT_AUTHORITY_INVALID が発生します。openssl s_client -showcerts でチェーン全体の有効期限を確認します。
期限切れを防ぐモニタリング
自動更新を設定していても、更新失敗を検知する仕組みがなければ期限切れは防げません。以下の多層的な監視が有効です。
certbot 自体のログ監視に加え、外部からの証明書有効期限チェックを組み合わせます。内部の certbot が「更新成功」と報告していても、実際にクライアントに配信されている証明書が古いままというケースがあるためです。
残り日数が 30 日 を切ったらアラートを出すのが一般的な閾値です。47 日証明書の時代に移行した後は、残り 14 日 や 7 日 での警告が必要になります。更新頻度が上がるため、アラートの閾値も証明書の有効期間に合わせて調整します。
Prometheus の blackbox_exporter や Datadog の SSL 監視は、外部からの定期的なチェックに適しています。Labee Dev Toolbox の SSL Cert API も、外部の視点から HTTPS 到達性を確認する手段として利用できます。