2026年5月19日

2026年5月19日

curlでサイトの死活監視をする簡易スクリプト|cronで定期実行・Slack通知

Web サイトが表示できなくなったことに早く気付くには、定期的にページへアクセスして結果を確かめる「死活監視」を行います。専用の監視サービスを使わなくても、curl とシェルスクリプト、cron があれば簡単な監視はできます。

この記事では、次のような動きをするスクリプトを作ります。

  • 指定した URL に curl でアクセスし、HTTP ステータスが 200 か、ページに決まった文字列が含まれているかを確認する
  • 失敗したら数秒おいて再試行し、続けて失敗したときだけ「異常」とする
  • 正常から異常になったとき、異常から復旧したときだけ通知する(毎分同じ通知が届かないようにする)

スクリプトは 2026年10月時点の Debian 13(bash 5.2、curl 8.14.1)で、正常・HTTP 404・名前解決の失敗・接続拒否・文字列が見つからない場合の動きを確認しています。

curlのインストール

多くの Linux ディストリビューションでは curl が最初から入っています。curl --version でバージョンが表示されなければインストールします。

# Debian / Ubuntu
sudo apt install curl

# RHEL / AlmaLinux / Rocky Linux
sudo dnf install curl

まずコマンドで確かめる

スクリプトの中心になるのは次の curl です。ページの本文は捨て、HTTP ステータスだけを表示します。

curl -sS -o /dev/null -w '%{http_code}\n' --connect-timeout 5 -m 10 https://example.com/
オプション 意味
-s 進み具合の表示を消す
-S -s を付けていてもエラーの内容は表示する
-o /dev/null 受け取った本文を捨てる
-w '%{http_code}\n' 終わったあとに HTTP ステータスを表示する
--connect-timeout 5 接続にかける時間の上限(秒)
-m 10 全体にかける時間の上限(秒)。応答が返ってこないときに、いつまでも待たないようにする

応答時間も見たい場合は、-w に時間の変数を加えます。

curl -sS -o /dev/null -w 'code=%{http_code} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/

旧版の記事では --head(HEAD リクエスト)を使っていましたが、サーバーやプラグインによっては HEAD と GET で応答が違うことがあります。利用者が見るのと同じ結果を確かめるため、この記事では通常の GET を使います。

curlの終了コードを読む

接続できなかったときなどは、-w '%{http_code}' は 000 を表示し、curl 自体が 0 以外の終了コードで終わります。原因の見当を付けるときに役立つ主な終了コードは次のとおりです。

終了コード 意味
0 通信は成功(HTTP ステータスは別に確認する)
6 ホスト名を解決できない(DNS の問題、ドメインの期限切れなど)
7 サーバーに接続できない(Web サーバーが止まっている、ファイアウォールなど)
22 -f(--fail)を付けたとき、HTTP ステータスが 400 以上だった
28 タイムアウトした
35 TLS(SSL)の接続でエラーが起きた
60 サーバー証明書を検証できない(期限切れ、証明書の設定誤りなど)

監視スクリプトを作る

次の内容を /usr/local/bin/site-check.sh として保存します。

#!/bin/bash
# curl でサイトの死活を確認し、異常になったときと復旧したときだけ通知する
set -u

URL="${1:-https://example.com/}"   # 監視するURL(引数で指定、なければこの値)
EXPECT_CODE="200"                  # 正常とみなすHTTPステータス
KEYWORD=""                         # ページに含まれるはずの文字列(空なら確認しない)
TIMEOUT=10                         # 1回の確認にかける最大秒数
RETRIES=3                          # 何回続けて失敗したら異常とするか
SLACK_WEBHOOK_URL=""               # Slack の Incoming Webhook の URL(空なら通知しない)

# 前回の結果をURLごとに保存するファイル(複数のURLを監視しても混ざらない)
STATE_FILE="/var/tmp/site-check.$(printf '%s' "$URL" | md5sum | cut -c1-12).state"

# 前回の確認がまだ終わっていなければ、今回は何もしない(同じURLの二重実行を防ぐ)
exec 9> "$STATE_FILE.lock"
flock -n 9 || exit 0

log() {
  echo "$(date '+%F %T') $*"
}

notify() {
  log "$1"
  if [ -n "$SLACK_WEBHOOK_URL" ]; then
    curl -sS -m 10 -X POST -H 'Content-type: application/json' \
      --data "{\"text\":\"$1\"}" "$SLACK_WEBHOOK_URL" > /dev/null
  fi
}

check_once() {
  local body code rc
  body=$(mktemp)
  code=$(curl -sS -o "$body" -w '%{http_code}' --connect-timeout 5 -m "$TIMEOUT" "$URL" 2>/dev/null)
  rc=$?
  if [ "$rc" -ne 0 ]; then
    REASON="curl exit $rc"
  elif [ "$code" != "$EXPECT_CODE" ]; then
    REASON="HTTP $code"
  elif [ -n "$KEYWORD" ] && ! grep -qF -- "$KEYWORD" "$body"; then
    REASON="keyword not found"
  else
    REASON=""
  fi
  rm -f "$body"
  [ -z "$REASON" ]
}

status="NG"
for i in $(seq 1 "$RETRIES"); do
  if check_once; then
    status="OK"
    break
  fi
  log "retry $i/$RETRIES: $URL ($REASON)"
  [ "$i" -lt "$RETRIES" ] && sleep 5
done

prev=$(cat "$STATE_FILE" 2>/dev/null || echo "OK")
echo "$status" > "$STATE_FILE"

if [ "$status" = "NG" ] && [ "$prev" = "OK" ]; then
  notify "[DOWN] $URL ($REASON)"
elif [ "$status" = "OK" ] && [ "$prev" = "NG" ]; then
  notify "[RECOVERED] $URL"
fi

[ "$status" = "OK" ]

スクリプトの動き

  • check_once が1回分の確認です。curl の終了コード、HTTP ステータス、ページ内の文字列(KEYWORD を指定したとき)の順に確かめ、問題があれば理由を REASON に入れます
  • 失敗したら5秒おいて再試行し、RETRIES 回続けて失敗したら「NG」とします。一時的な通信の乱れで通知が飛ぶのを減らすためです
  • 前回の結果を STATE_FILE に保存し、「OK → NG」になったときだけ [DOWN]、「NG → OK」になったときだけ [RECOVERED] を通知します
  • flock で、同じ URL の確認が前回からまだ続いている場合は何もせずに終わります。毎分実行しても、確認が重なりません
  • 異常のときは終了コード 1 で終わるので、ほかのスクリプトから結果を使うこともできます

KEYWORD には、正常なページに表示される文字列(サイト名やフッターの文言など)を指定します。HTTP ステータスが 200 でも、データベースにつながらないエラー画面や、改ざんされたページを検出しやすくなります。

Slackに通知する

Slack に通知するには、Slack で Incoming Webhook を作り、発行された URL を SLACK_WEBHOOK_URL に設定します。Webhook の URL は、知っている人なら誰でもそのチャンネルに投稿できるので、スクリプトのファイルを他のユーザーが読めないようにしてください。

通知するメッセージは、URL と理由を " で囲んで JSON にしています。" を含む URL を監視する場合は、JSON が壊れるので書き方を変える必要があります。

実行権限を付けて試す

sudo chmod 700 /usr/local/bin/site-check.sh
sudo /usr/local/bin/site-check.sh https://example.com/
echo $?

正常なら何も表示されず、終了コードは 0 です。存在しないページを指定すると、次のように再試行のログと [DOWN] が表示されます。

2026-10-29 17:18:18 retry 1/3: https://example.com/nonexistent-zzz (HTTP 404)
2026-10-29 17:18:23 retry 2/3: https://example.com/nonexistent-zzz (HTTP 404)
2026-10-29 17:18:28 retry 3/3: https://example.com/nonexistent-zzz (HTTP 404)
2026-10-29 17:18:28 [DOWN] https://example.com/nonexistent-zzz (HTTP 404)

同じ状態でもう一度実行すると、再試行のログは出ますが [DOWN] は出ません。正常な URL に戻ると [RECOVERED] が表示されます。

cronで1分ごとに実行する

sudo crontab -e で root の crontab を開き、次の行を追加します。URL ごとに1行ずつ書けば、複数のサイトを監視できます。

* * * * * /usr/local/bin/site-check.sh https://example.com/ >> /var/log/site-check.log 2>&1
* * * * * /usr/local/bin/site-check.sh https://example.org/ >> /var/log/site-check.log 2>&1

この設定では、1回の確認に最大で約45秒(10秒のタイムアウト×3回+待ち時間5秒×2回)かかります。TIMEOUT や RETRIES を大きくして1分を超えても、flock により同じ URL の確認は重なりません。

ログは次のように確認します。

tail -f /var/log/site-check.log

ログは少しずつ大きくなるので、長く使う場合は logrotate の対象にしてください。

注意点

  • 監視するサーバーの場所:監視対象と同じサーバーでスクリプトを動かすと、サーバー自体が止まったときやネットワークが切れたときに通知できません。別のサーバーから監視するか、外部の監視サービスと組み合わせてください
  • アクセスの頻度:1分ごとのアクセスは、監視対象のアクセスログにも残ります。アクセス解析から除外したい場合は、curl の -A オプションで分かりやすい User-Agent を付けておくと見分けやすくなります
  • リダイレクト:http:// から https:// へ転送されるサイトを http:// で監視すると、ステータスは 301 などになります。最終的に表示される URL を指定するか、curl に -L を付けてリダイレクト先まで確認します
  • 証明書:証明書の期限切れは終了コード 60 などで検出されますが、期限が近いことの事前通知はこのスクリプトではできません

まとめ

  • curl -sS -o /dev/null -w '%{http_code}' で HTTP ステータスを取り、--connect-timeout と -m で待ち時間の上限を決める
  • 接続できないときは curl の終了コード(6・7・28・60 など)で原因の見当を付ける
  • 再試行と前回の状態の保存で、異常になったときと復旧したときだけ通知する
  • flock で二重実行を防ぎ、cron で1分ごとに実行する
  • 同じサーバーからの監視ではサーバー全体の停止を検出できないので、別のサーバーや外部サービスと組み合わせる

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲