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分ごとに実行する- 同じサーバーからの監視ではサーバー全体の停止を検出できないので、別のサーバーや外部サービスと組み合わせる
関連記事: