Claude CodeでSSL証明書の期限台帳をopenssl s_clientで作る
ホスト一覧にopenssl s_clientで接続し、サーバー証明書の有効期限と発行者を期限が近い順の表にします。担当者の割り当てと、Claude Codeに任せる範囲まで書きます。
Webサイトや社内APIの数が増えると、サーバー証明書の期限がどの担当者の頭にあるのか分からなくなります。期限切れはブラウザの警告やAPI接続の失敗としてすぐ表に出るため、事前に全ホストの期限を1枚の表に集め、近い順に並べておくのが確実です。
その表は、openssl s_client でホストに接続して証明書を受け取り、openssl x509 で期限と発行者を読めば作れます。この記事では、ホスト一覧から台帳CSVを出すシェルスクリプトと、結果の読み方、Claude Codeに任せる範囲を順に書きます。ドメイン登録そのものの期限はRDAPでのドメイン期限台帳が別の主題で、ここで扱うのはサーバーが提示する証明書です。
台帳に持つ列と、取得の流れ
サーバー証明書の台帳は、次の5列で足ります。
| 列 | 中身 | 取り方 |
|---|---|---|
| not_after | 中身証明書の有効期限 | 取り方openssl x509 -enddate |
| host | 中身接続先のホスト名 | 取り方ホスト一覧から |
| issuer | 中身発行者 | 取り方openssl x509 -issuer |
| owner | 中身更新担当 | 取り方ホスト一覧から |
| status | 中身OK / EXPIRING / UNREACHABLE | 取り方-checkend と接続結果 |
流れは3段です。
ホスト1行あたりの処理
- 1
証明書を受け取る
openssl s_client -connect host:443で接続し、サーバーが送る証明書を標準出力に出します。 - 2
期限と発行者を読む
出力を
openssl x509に渡し、-enddateと-issuerで値を取り出します。 - 3
残り日数で判定する
-checkendに秒数を渡し、その期間内に失効するかを終了コードで受けます。
s_clientで証明書を受け取る
openssl s_client は、TLSサーバーに接続して接続情報と証明書を表示するコマンドです。公式のNOTESには、SSLサーバーのデバッグに使えるコマンドで、HTTPSなら openssl s_client -connect servername:443 のように使うと書かれています。
台帳づくりで効くオプションは次のとおりです。
-connect host:port: 接続先です。ポートを省くと手元のホストの4433番に接続しにいくので、ポートまで書きます-servername name: TLSのSNI(接続先ホスト名の通知)に入れる値です。-connectにDNS名を渡せば自動で入りますが、IPアドレスで接続するときは明示します-showcerts: サーバーが送った証明書のリストを送られた順に表示します。検証済みのチェーンではない点に注意が必要です-starttls protocol: SMTPやIMAPなど、平文で始まってTLSに切り替わるプロトコルで使います。smtp・imap・pop3・ldapなどのキーワードが使えます
SNIの扱いは台帳の精度に直結します。同じIPアドレスで複数のサイトを動かしている場合、SNIが違うと別の証明書が返ります。ホスト一覧にはIPではなくホスト名を書き、-servername にも同じ名前を渡します。
検証は台帳の役目ではない
s_clientは検証エラーがあっても接続を続ける作りです。公式のNOTESは、このコマンドはテスト用で、証明書の検証エラーの後もハンドシェイクを続けるため、信頼されていないチェーンも受け入れると説明しています。-verify_return_error を付けると、検証エラーで中断します。
台帳の目的は「いつ切れるか」の把握なので、期限切れや自己署名の証明書も取得できるほうが都合がよく、検証オプションは付けません。信頼できる証明書かどうかの判定は、別の確認として扱います。
台帳CSVを出すスクリプト
ホスト一覧は、host[:port],担当者 の2列のCSVにします。ポートを省くと443番です。
www.example.com,web-team
api.example.com,platform-team
mail.example.com:993,infra-team次のスクリプトが、一覧の各行について証明書を取り、台帳の1行にします。
#!/usr/bin/env bash
# 使い方: ./check.sh hosts.csv > ledger.csv
echo "not_after,host,issuer,owner,status"
while IFS=, read -r target owner; do
[ -z "$target" ] && continue
host=${target%%:*}; port=${target##*:}
[ "$port" = "$target" ] && port=443
cert=$(openssl s_client -connect "$host:$port" \
-servername "$host" </dev/null 2>/dev/null \
| openssl x509 2>/dev/null)
if [ -z "$cert" ]; then
echo "9999-12-31,$host,,$owner,UNREACHABLE"; continue
fi
end=$(openssl x509 -noout -enddate -dateopt iso_8601 \
<<<"$cert" 2>/dev/null | cut -d= -f2)
issuer=$(openssl x509 -noout -issuer <<<"$cert" \
2>/dev/null | sed 's/^issuer= *//; s/,/;/g')
openssl x509 -noout -checkend $((3600*24*30)) \
<<<"$cert" >/dev/null 2>&1 && st=OK || st=EXPIRING
echo "$end,$host,$issuer,$owner,$st"
done < "$1"設計上の要点は4つです。
</dev/nullで標準入力を閉じ、s_clientが対話待ちにならないようにしていますopenssl x509に素の出力を渡すと、証明書のPEMブロックだけが取り出されます- 接続できない行は捨てずに
UNREACHABLEで残し、ソート時に末尾へ回るよう日付を9999-12-31にしています - 発行者のDNに含まれるカンマは
;に置き換え、CSVの列が崩れないようにしています
期限が近い順に並べるには、日付が先頭列のISO形式なので、文字列のソートで足ります。
./check.sh hosts.csv | (read -r h; echo "$h"; sort -t, -k1,1) > ledger.csv手元で確かめた出力
ローカルで自己署名証明書(有効20日)を作り、openssl s_server で待ち受けさせてスクリプトに通すと、次の2行が出ました(OpenSSL 3.2.1で実行。2行目は閉じているポートを指定した行です)。
not_after,host,issuer,owner,status
2026-11-09 05:11:50Z,localhost,CN=localhost; O=Example Org,infra-team,EXPIRING
9999-12-31,localhost,,web-team,UNREACHABLE同じ証明書に -checkend を30日で当てると終了コードが1になり、標準出力に Certificate will expire が出ます。この終了コードが status 列の判定の根拠です。
環境による違いと取得が欠けるとき
-dateopt iso_8601 は、OpenSSL 3系の x509 コマンドの引数に載っているオプションです。macOS標準のLibreSSLなど別実装では使えないことがあるため、台帳を回す環境のOpenSSLで先に動かしてください。
取得が欠ける場合の手がかりは、公式の「Note on Non-Interactive Use」にあります。cronやスクリプトのように有効な標準入力が無い環境では、s_clientが接続を早く閉じることがあり、特にTLS 1.3で起こるとされています。その対策として -ign_eof が挙げられていますが、サーバー側がクライアントの切断を待つ場合は止まったままになりうるため、使うなら実行時間の上限も合わせて決めます。
台帳の読み方と更新担当の割り当て
出力は、期限が近い順に上から読みます。見るのは3点です。
EXPIRING: 30日以内に失効する行です。上の-checkendの秒数を変えれば、14日や60日にも変えられますUNREACHABLE: 証明書を取れなかった行です。ホストの停止、ポート違い、ファイアウォール、SNI必須のサーバーへの接続などが疑われます。期限が分からないだけで、安全という意味ではありません- 同じ発行者の行がまとまって近い: 自動更新の仕組み(ACMEなど)が止まっている可能性があります。1台ずつ直すより、更新の仕組みを先に見ます
owner 列は、台帳の価値を決める列です。期限が見えても、誰が更新するのか決まっていなければ期限切れは止まりません。ホスト一覧の段階で担当者を書き、担当者が空の行は台帳づくりの時点で弾くと、割り当て漏れを早く見つけられます。
落とし穴
1つのホストに複数の証明書がある
ポートごと、あるいはSNIごとに別の証明書を持つサーバーがあります。ホスト一覧を host:port の単位で書き、IP直指定の行は作らないようにします。メール(993番など)は -starttls が要るポートもあるため、まず対象のプロトコルを確認します。
Claude Codeに任せる範囲と設定
台帳づくりのうち、Claude Codeに任せやすいのは次の作業です。
- ホスト一覧の整形(重複の除去、ポートの書き足し、担当者の空欄の洗い出し)
- スクリプトの実行と、
UNREACHABLE行ごとの原因の切り分け案 - 前回の台帳との差分(新しく期限が近づいた行、消えた行)の要約
期限をいつ更新するかの判断、担当者の決定、証明書の再発行は人が持ちます。
実行を許可する範囲
Claude Codeの権限ルールは、拒否、確認、許可の順に評価され、最初に一致したものが結果になります。台帳の用途なら、s_client を自由に打たせるより、スクリプトだけを許可するほうが範囲を絞れます。
{
"permissions": {
"allow": [
"Bash(./check.sh *)",
"Read(./hosts.csv)",
"Read(./ledger.csv)"
]
}
}公式の権限ドキュメントでは、Bashルールは * の位置までを書いたとおりに照合し、Bash(git log *) のように末尾に置いたパターンは、そのコマンドで始まる呼び出しに一致します。同じドキュメントは、引数を絞るパターンは壊れやすいとも説明しているため、「このホストだけ」の制限をルールで表そうとはせず、許可するのはスクリプトの実行までにとどめます。接続先の範囲は、ホスト一覧のファイルで管理します。
CLAUDE.mdに書く規約
台帳づくりを毎回同じ手順で動かすには、プロジェクトのCLAUDE.mdに規約を置きます。記述の例を挙げます。
## 証明書台帳
- 台帳は `./check.sh hosts.csv` の出力を、期限が近い順にソートして作る
- `hosts.csv` に無いホストへは接続しない
- 検証オプション(`-verify_return_error`)は付けない。期限の把握が目的
- UNREACHABLE の行は削除せず、原因の候補を添えて報告する
- 出力の行数が `hosts.csv` の行数と一致することを確かめる最後の行数の一致は、Claude Codeに作業を任せるときの最も安い検算です。スクリプトが1行も落とさない作りなので、一致しなければ処理が途中で止まったことになります。
定期実行にして、台帳を古くしない
台帳は、作った日から古くなります。更新が済んだ証明書の期限は延び、新しいホストは増えます。週1回ほどの頻度でスクリプトを回し、EXPIRING の行があれば担当者に通知する運用にすると、期限日の前に気づけます。
通知の送り方は環境によって違うため、ここではスクリプトの出力を EXPIRING で絞り込む部分までを示します。
./check.sh hosts.csv | grep -E ',(EXPIRING|UNREACHABLE)$'cronなどから回す場合は、前述のとおり標準入力が無い環境でのs_clientの挙動に注意します。
隣接する台帳との使い分け
期限を持つ対象は、証明書のほかにも複数あります。
- 公開するサーバー証明書: この記事の台帳
- ドメインの登録期限: RDAPで引く
- Entraなどのクライアントシークレット: Entraのシークレット期限の一覧化の手順
- 機器やソフトウェアのサポート終了日: endoflife.date APIでの突合
Claude Code自体が証明書エラーで止まっているときは、Claude CodeのSSL証明書エラーが対象です。社内の認証局のクライアント証明書をClaude Codeに使わせる設定は、mTLSクライアント証明書の記事にまとめています。
まとめ
サーバー証明書の期限台帳は、ホスト一覧の各行に openssl s_client で接続し、受け取った証明書を openssl x509 で読むだけで作れます。接続できない行も捨てずに残すのが、台帳を信用できる状態に保つ条件です。
Claude Codeに渡すのは、スクリプトの実行許可、CLAUDE.mdの規約、行数の突き合わせまでです。期限の判断と更新担当は人が持ちます。