シェルスクリプトでSSL証明書の有効期限を自動監視する設計|opensslとcronで期限切れを防ぐ実践パターン

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトでSSL証明書の有効期限を自動監視する設計|opensslとcronで期限切れを防ぐ実践パターン
「SSL証明書の更新を自動化していたはずなのに、気づいたら期限が切れていた」
「Let'sEncryptのcertbotが失敗していたのに、誰も知らなかった」

証明書の期限切れはれっきとした障害だ。しかしその多くは「更新忘れ」ではなく「失敗に気づかない」という仕組みの問題で起きる。certbotによる自動更新を設定していても、HTTP-01チャレンジの失敗やポートブロックで更新できないケースがある。複数のドメインを担当していれば、管理台帳と実態がずれていることも珍しくない。

この記事では、opensslコマンドとシェルスクリプトを組み合わせて、複数ドメインのSSL証明書有効期限を毎日自動チェックし、期限が近づいたらメールで通知する監視スクリプトを設計する。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みの実装だ。

この記事のポイント

・openssl s_clientで証明書の残日数を取得しシェル変数で管理できる
・set -euo pipefailとtrapを組み合わせて失敗を確実に検知できる
・30日前・7日前など警告レベルを分けてメール通知の優先度を設計できる
・複数ドメインをファイル管理して一括チェックするスクリプトを構築できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜ証明書監視をシェルスクリプトで自動化すべきか

証明書の期限管理が「人手による定期確認」に頼っていると、3つの問題が起きやすい。

担当者の異動でレビューが抜ける: カレンダーに登録しても、担当者が変わった途端に形骸化する
certbotの更新失敗が見えない: cronで動かしていても、HTTP-01チャレンジ失敗やファイアウォール設定で更新できないことがある
管理台帳と実態のズレ: 複数ドメインを管理していると、台帳上は更新済みでも古い証明書が使われているケースが起きる

シェルスクリプトで自動監視を設計すれば、cronが毎日証明書の状態を確認し、期限が近づいたときだけアラートを送る仕組みが手に入る。担当者が変わっても、台帳を更新しなくても、実際に接続してリアルタイムに確認するから漏れが起きない。

20年以上Linux環境を運用してきた経験から言うと、「気づいたら証明書が切れていた」という障害の多くは、確認の仕組みを作っていなかったことが原因だ。手順書ではなくスクリプトで仕組みを作ることが、継続的な安定運用の第一歩になる。

opensslで証明書の有効期限を取得する基本操作

スクリプトを設計する前に、opensslコマンドで期限を取得する基本を押さえておこう。

1. 接続して有効期限を確認する

# ドメインの証明書有効期限を取得する echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -enddate # 出力例 notAfter=Sep 14 12:00:00 2026 GMT

-servernameオプションはSNI(Server Name Indication)対応のために必要だ。1つのIPアドレスに複数ドメインを載せている環境では、このオプションなしでは正しい証明書を取得できないことがある。

2. 残日数をシェルで計算する

#!/bin/bash HOST="example.com" # 有効期限の取得(「notAfter=...」の値を切り出す) expire_str=$(echo | openssl s_client -connect "${HOST}:443" -servername "${HOST}" 2>/dev/null \ | openssl x509 -noout -enddate | cut -d= -f2) # エポック秒に変換して差分を計算 expire_epoch=$(date -d "${expire_str}" +%s) today_epoch=$(date +%s) days_left=$(( (expire_epoch - today_epoch) / 86400 )) echo "${HOST}: 残 ${days_left} 日" # 出力例: example.com: 残 30 日

date -dでGMT形式の日付をエポック秒に変換してから差分を取ることで、タイムゾーンのズレを気にせず計算できる。この計算ロジックがスクリプトの核心になる。

証明書監視スクリプトの設計を含むシェルスクリプトの実践力を体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。設計パターンをハンズオンで習得できます。

証明書監視スクリプトを設計する

実際のスクリプトを段階的に設計していく。

1. trapとset -euo pipefailで堅牢化する

接続失敗や証明書取得のエラーが出ても、スクリプトが黙って続行するのは危険だ。エラーを確実に捕捉する基盤から作る。

#!/bin/bash # cert-monitor.sh - SSL証明書有効期限監視スクリプト # 動作確認: RHEL 9.4 / Ubuntu 24.04 LTS set -euo pipefail # 設定値(環境に合わせて変更する) WARN_DAYS_CRITICAL=7 # 緊急アラートの閾値(日) WARN_DAYS_WARNING=30 # 警告アラートの閾値(日) ALERT_TO="admin@example.com" LOG_FILE="/var/log/cert-monitor.log" DOMAIN_LIST="/etc/cert-monitor/domains.txt" # ワーク用一時ファイル(trapで確実に削除する) TMPFILE="" cleanup() { [[ -n "${TMPFILE:-}" ]] && rm -f "${TMPFILE}" } trap cleanup EXIT ERR TMPFILE=$(mktemp /tmp/cert-monitor.XXXXXX)

set -euo pipefailの各フラグの意味を整理しておく。

-e: コマンドが失敗(exit code 0以外)したら即座に終了する
-u: 未定義の変数を参照したらエラーにする
-o pipefail: パイプライン途中のコマンドが失敗した場合もエラーとして検知する

trap cleanup EXIT ERRを設定することで、スクリプトが正常終了した場合も、途中でエラーが起きた場合も、必ずcleanup関数が実行される。一時ファイルがサーバー上に残り続けることを防ぐ基本設計だ。

2. 証明書チェック関数を設計する

個々のドメインへの接続失敗をスクリプト全体の終了につなげないよう、チェック処理を関数に切り出す。

# ログ出力関数(標準出力とログファイルの両方に書く) log() { local level="$1" shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "${LOG_FILE}" } # 証明書の残日数を返す関数(取得失敗時は -1 を返す) get_days_left() { local host="$1" local expire_str # timeout 10 で接続タイムアウトを設定する expire_str=$(timeout 10 bash -c \ "echo | openssl s_client -connect ${host}:443 -servername ${host} 2>/dev/null \ | openssl x509 -noout -enddate" 2>/dev/null | cut -d= -f2) || { echo "-1" return } if [[ -z "${expire_str}" ]]; then echo "-1" return fi local expire_epoch today_epoch expire_epoch=$(date -d "${expire_str}" +%s 2>/dev/null) || { echo "-1"; return; } today_epoch=$(date +%s) echo $(( (expire_epoch - today_epoch) / 86400 )) }

取得失敗時に-1を返す設計がポイントだ。set -eが有効な環境でも、関数内では|| { echo "-1"; return; }で失敗を受け止め、呼び出し元にエラー値として渡す。ある1ドメインで接続が失敗しても、他のドメインのチェックが止まらない。

3. 警告レベル別の通知設計

残日数によって通知の緊急度を変える。件名で緊急度が伝わるよう設計する。

# メール通知関数 send_alert() { local host="$1" local days="$2" local level="$3" local subject="【SSL証明書${level}】${host}: 残${days}日" log "ALERT" "${subject}" { echo "${host} のSSL証明書が ${days} 日後に期限切れになります。" echo "更新作業を実施してください。" echo "" echo "確認コマンド:" echo " echo | openssl s_client -connect ${host}:443 -servername ${host} 2>/dev/null \" echo " | openssl x509 -noout -dates" echo "" echo "実行日時: $(date '+%Y-%m-%d %H:%M:%S')" } | mail -s "${subject}" "${ALERT_TO}" } # 残日数に応じて通知を分岐する関数 check_and_alert() { local host="$1" local days days=$(get_days_left "${host}") if [[ "${days}" -eq -1 ]]; then log "ERROR" "${host}: 証明書取得失敗(接続エラーまたは証明書なし)" send_alert "${host}" "不明" "取得エラー" || true return fi log "INFO" "${host}: 残 ${days} 日" if [[ "${days}" -le "${WARN_DAYS_CRITICAL}" ]]; then send_alert "${host}" "${days}" "緊急" elif [[ "${days}" -le "${WARN_DAYS_WARNING}" ]]; then send_alert "${host}" "${days}" "警告" fi }

7日以内は「緊急」、30日以内は「警告」として、件名で優先度を伝える設計だ。メールを開かなくても件名だけで緊急度を把握できるため、夜間の通知であっても対応の判断ができる。

4. 複数ドメインの一括チェック設計

監視対象のドメインをファイルで管理し、スクリプトから読み込む。

# /etc/cert-monitor/domains.txt の内容例 # 1行1ドメイン。# で始まる行はコメントとしてスキップされる example.com www.example.com api.example.com # shop.example.com # 一時停止中 # メイン処理 main() { log "INFO" "証明書監視を開始します" if [[ ! -f "${DOMAIN_LIST}" ]]; then log "ERROR" "ドメインリストが見つかりません: ${DOMAIN_LIST}" exit 1 fi local error_count=0 while IFS= read -r host || [[ -n "${host}" ]]; do # 空行とコメント行をスキップ [[ -z "${host}" || "${host}" =~ ^# ]] && continue check_and_alert "${host}" || (( error_count++ )) || true done < "${DOMAIN_LIST}" log "INFO" "証明書監視が完了しました(エラー数: ${error_count})" } main

ドメインリストをファイルで管理する設計にすることで、監視対象の追加・削除がスクリプト本体を編集せずにできる。コメント行(#始まり)は自動的にスキップするため、一時的に外したいドメインはコメントアウトするだけでよい。

cronに組み込んで毎日自動実行する

スクリプトを/opt/scripts/cert-monitor.shに配置し、実行権限を付けてからcronに登録する。

# スクリプトの配置と権限設定 sudo install -m 750 -o root cert-monitor.sh /opt/scripts/cert-monitor.sh # ドメインリストの作成 sudo mkdir -p /etc/cert-monitor sudo tee /etc/cert-monitor/domains.txt << 'DOMAINS' example.com www.example.com api.example.com DOMAINS # cronへの登録(毎朝9時に実行) # sudo crontab -e で以下の1行を追加する: # 0 9 * * * root /opt/scripts/cert-monitor.sh 2>&1

logrotateで30日分のログを自動ローテーションする設定も入れておく。

# /etc/logrotate.d/cert-monitor /var/log/cert-monitor.log { daily rotate 30 compress missingok notifempty }

cronに登録する前に、コマンドラインから直接実行して動作確認しておくこと。

# テスト実行 sudo /opt/scripts/cert-monitor.sh # ログ確認 tail -f /var/log/cert-monitor.log # 実行結果の例 [2026-08-15 09:00:01] [INFO] 証明書監視を開始します [2026-08-15 09:00:03] [INFO] example.com: 残 82 日 [2026-08-15 09:00:05] [INFO] www.example.com: 残 82 日 [2026-08-15 09:00:07] [INFO] api.example.com: 残 29 日 [2026-08-15 09:00:07] [ALERT] 【SSL証明書警告】api.example.com: 残29日 [2026-08-15 09:00:09] [INFO] 証明書監視が完了しました(エラー数: 0)

接続エラーとよくあるトラブル対処

1. 接続タイムアウトでスクリプトが止まる

openssl s_clientにはデフォルトのタイムアウトがない。外部からアクセスできない内部専用ドメインや、ファイアウォールで443番ポートがブロックされているサーバーでは、コマンドが無限に待ち続ける。

設計例ではtimeout 10コマンドでラップして10秒以内に応答がなければ強制終了するようにしている。timeoutコマンドはcoreutils提供のため、RHEL 9 / Ubuntu 24.04 LTSでは追加インストール不要だ。

2. 自己署名証明書で検証エラーが出る

ステージング環境や内部サーバーで自己署名証明書を使っている場合、openssl s_clientverify errorstderrに出力する。スクリプト内では2>/dev/nullで抑制しているため、証明書情報の取得自体には影響しない。CA証明書を検証してから期限を確認したい場合は-CAfile /etc/pki/tls/certs/ca-bundle.crtオプションを追加する。

3. mailコマンドが使えない環境

mailコマンドはデフォルトでインストールされていない環境がある。Postfixが設定されているサーバーであればsendmailコマンドも使える。SlackやChatworkへのWebhook通知に変えたい場合は、send_alert関数内のmailコマンドをcurl呼び出しに置き換えるだけで対応できる。

# Slack Webhookで通知する場合の例 SLACK_WEBHOOK="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX" send_slack_alert() { local message="$1" curl -s -X POST -H 'Content-type: application/json' \ --data "{"text": "${message}"}" \ "${SLACK_WEBHOOK}" > /dev/null }

本記事のまとめ

設計要素 実装方法
証明書の残日数取得 openssl s_client | openssl x509 -noout -enddateとdateでエポック差分計算
堅牢化 set -euo pipefailtrap cleanup EXIT ERRの組み合わせ
エラー耐性 個別ドメインの取得失敗は-1で受け止め他ドメインのチェックを継続
警告レベル分岐 残7日以内=緊急・残30日以内=警告で件名に緊急度を付与
監視対象管理 ドメインリストをファイルで外部管理(コメント行スキップ対応)
定期実行 cronで毎朝自動実行+logrotateでログローテーション
証明書の期限切れは防げる障害だ。「更新し忘れた」ではなく「スクリプトが知らせてくれなかった」という仕組みの問題として扱うことが、安定運用の考え方だ。一度設計してcronに組み込んでしまえば、あとは通知を受け取って対応するだけになる。監視を仕組みで解決する習慣が、現場での信頼につながる。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。