SSL 証明書の期限切れを防ぐために知っておくべきこと — 自動更新の落とし穴と対策
SSL 証明書の期限切れは、ブラウザーに警告画面を表示させ、事実上のサイトダウンを引き起こします。この記事では、openssl コマンドによる有効期限の確認方法、外部からの HTTPS 到達確認、そして自動更新が失敗する代表的なパターンと対策を解説します。
期限切れが引き起こす影響
SSL 証明書が期限切れになると、ブラウザーは「この接続ではプライバシーが保護されません」という警告を表示します。ユーザーの大半はこの警告を見た時点で離脱するため、サービスが稼働していてもアクセスできない状態と変わりません。
Let’s Encrypt の普及で証明書の取得は無料かつ自動化できるようになりました。しかし自動更新の仕組みが正しく動作しているかを誰も確認していないまま期限切れを迎えるケースは、今でも頻繁に発生しています。
CA/Browser Forum は 2025 年 4 月に Ballot SC-081v3 を可決し、TLS 証明書の最大有効期間を段階的に短縮するスケジュールを決定しました。2026 年 3 月 15 日以降は最大 200 日、2027 年 3 月以降は最大 100 日、2029 年 3 月以降は最大 47 日 になります。Let’s Encrypt は 2026 年 5 月 13 日からオプトインの新プロファイルで 45 日 証明書の発行を開始し、2028 年 2 月 16 日にはデフォルトプロファイルでも 45 日証明書が標準になる予定です。証明書の寿命が短くなるほど、自動更新の信頼性が直接サービスの可用性を左右します。
openssl で有効期限を確認する
手元で証明書の有効期限を確認するには openssl を使います。
openssl s_client -connect example.com:443 -brief 2>/dev/null | head -5
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = example.com
Verification: OK
接続が確立できたら、有効期限の日付を取得します。
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Jan 13 00:00:00 2026 GMT
notAfter=Feb 13 23:59:59 2027 GMT
notAfter が証明書の有効期限です。残り日数を計算するには次のコマンドを使います。
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -enddate \
| cut -d= -f2 \
| xargs -I {} date -d {} +%s \
| xargs -I {} bash -c 'echo $(( ({} - $(date +%s)) / 86400 )) days remaining'
openssl での確認にはいくつかの制約があります。ローカルのネットワークから接続するため、CDN やロードバランサーの構成によっては実際のユーザーとは異なる証明書が返ることがあります。社内ネットワークや VPN 経由だと接続自体が成功しても、外部からは到達できない場合もあります。
外部からの HTTPS 到達確認
openssl の確認だけでは「外部のユーザーが実際に HTTPS でアクセスできるか」を保証できません。Labee Dev Toolbox の SSL API を使うと、外部から HTTPS 接続が成立するかを確認できます。example.com を自分のドメインに置き換えて実行してみてください。
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": 312 }
}
data.reachable が true であれば、外部からの HTTPS 接続が成立しています。data.status は HTTP ステータスコードです。
接続に失敗した場合は次のようなレスポンスが返ります。
{
"success": false,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": false
},
"error": "Unable to connect",
"meta": { "responseTime": 0 }
}
reachable が false の場合、証明書の期限切れだけでなく、ファイアウォールやセキュリティーグループの設定が原因で外部からの接続自体が成立していない可能性があります。openssl と組み合わせて使うことで「証明書は有効だが外部から到達できない」問題と「証明書自体が期限切れ」の問題を区別できます。
ポート番号を指定する場合は port パラメーターを追加します。指定できるポートは 443, 2053, 2083, 2087, 2096, 4443, 8443 です。
curl "https://labee.dev/api/ssl-cert?hostname=example.com&port=8443"
自動更新が失敗する代表的なパターン
サーバーで証明書の自動更新を設定している場合でも、以下のパターンで更新が失敗します。ここでは Let’s Encrypt と Certbot の組み合わせを例に解説します。
HTTP 検証パスがブロックされている
Certbot はデフォルトで HTTP-01 チャレンジを使い、/.well-known/acme-challenge/ パスにトークンファイルを配置します。Web サーバーやリバースプロキシーのルールでこのパスへのアクセスが遮断されていると、検証が失敗します。nginx で特定パス以外を deny している設定や、.htaccess のリダイレクトルールが原因になることが多いパターンです。
Web サーバーのリロードが実行されていない
Certbot は証明書ファイルをディスクに書き込みます。一方、nginx や Apache は起動時にファイルを読み込んでメモリーに保持する仕組みです。reload が実行されていないと、ディスク上の証明書が更新されていてもプロセスは古い証明書を返し続けます。--deploy-hook や --post-hook で reload を指定してください。
certbot renew --deploy-hook "systemctl reload nginx"
ファイル権限の変更
Certbot は /etc/letsencrypt/ ディレクトリーに証明書ファイルを書き込みます。OS のアップデートやセキュリティー設定の変更でディレクトリーの権限が変わると、書き込みに失敗します。ログには記録されるものの、監視していなければ気づかないまま期限切れを迎えることになります。
DNS の CAA レコードによる拒否
CAA(Certification Authority Authorization)レコードは、そのドメインに対して証明書を発行できる認証局を制限する DNS レコードです。Let’s Encrypt(letsencrypt.org)が許可されていなければ、更新は拒否されます。他の認証局から乗り換えた際に CAA レコードを更新し忘れるケースが典型的です。
Let’s Encrypt のレート制限
同一ドメインに対する証明書の発行には週 50 件 の上限があります。ステージング環境のテストで本番ドメインを使い証明書を大量に発行すると、本番の更新がレート制限に引っかかります。テストには --staging オプションを使ってください。
定期的な自動チェックの仕組み
手動での確認だけでは、チェックを忘れた期間に期限切れが発生するリスクがあります。openssl と API を組み合わせた定期チェックを CI 環境で実行するのが効果的です。
openssl で残り日数を取得し、閾値を下回ったらアラートを出すスクリプトの例です。
#!/bin/bash
DOMAIN="example.com"
THRESHOLD=30
expiry=$(echo | openssl s_client -connect "${DOMAIN}:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "${expiry}" +%s)
now_epoch=$(date +%s)
days_remaining=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "${days_remaining}" -lt "${THRESHOLD}" ]; then
echo "WARNING: ${DOMAIN} の証明書は残り ${days_remaining} 日で期限切れです"
exit 1
fi
echo "OK: ${DOMAIN} の証明書は残り ${days_remaining} 日"
外部からの到達確認も合わせて実行すると、証明書の有効期限と HTTPS の到達性の両方を監視できます。
result=$(curl -fsS "https://labee.dev/api/ssl-cert?hostname=example.com")
reachable=$(echo "${result}" | jq '.data.reachable')
if [ "${reachable}" != "true" ]; then
echo "ALERT: example.com に外部から HTTPS で到達できません"
exit 1
fi
この 2 つのスクリプトを GitHub Actions の cron: '0 9 * * *' で毎日実行すれば、証明書の期限切れと外部到達性の問題を早期に検出できます。API キーは不要なので、Secrets の設定なしで動作します。
GUI で到達確認する場合は Labee Dev Toolbox の SSL 到達確認画面にホスト名を入力するだけで、外部からの HTTPS 接続状況を確認できます。