2026年11月13日
2026年11月13日
Simple Uptime Monitoring Script with curl: cron and Slack Alerts
This article is a localized edition of the Japanese original. Read the Japanese original
To find out quickly when a website stops responding, you run "uptime monitoring": access the page at regular intervals and check the result. You do not need a dedicated monitoring service for basic checks; curl, a shell script and cron are enough.
In this article you will build a script that works like this:
- It requests the given URL with
curland checks that the HTTP status is 200 and, optionally, that the page contains a specific string - On failure it waits a few seconds and retries, and only treats the site as "down" after consecutive failures
- It notifies you only when the status changes from healthy to down and from down to recovered (so you do not get the same alert every minute)
The script was tested as of October 2026 on Debian 13 (bash 5.2, curl 8.14.1) for these cases: healthy, HTTP 404, name resolution failure, connection refused, and missing keyword.
Installing curl
Most Linux distributions ship with curl. If curl --version does not print a version, install it.
# Debian / Ubuntu
sudo apt install curl
# RHEL / AlmaLinux / Rocky Linux
sudo dnf install curl
Try the command first
The heart of the script is the following curl command. It discards the page body and prints only the HTTP status.
curl -sS -o /dev/null -w '%{http_code}\n' --connect-timeout 5 -m 10 https://example.com/
| Option | Meaning |
|---|---|
-s |
Hide the progress meter |
-S |
Still show error messages even with -s |
-o /dev/null |
Discard the response body |
-w '%{http_code}\n' |
Print the HTTP status when the transfer finishes |
--connect-timeout 5 |
Maximum time for connecting (seconds) |
-m 10 |
Maximum time for the whole operation (seconds), so you do not wait forever when no response comes back |
To see response times as well, add timing variables to -w.
curl -sS -o /dev/null -w 'code=%{http_code} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
The old version of this article used --head (a HEAD request), but some servers and plugins answer HEAD and GET differently. To check what visitors actually get, this article uses a normal GET.
Reading curl exit codes
When the connection fails, -w '%{http_code}' prints 000 and curl itself exits with a non-zero code. These exit codes help you narrow down the cause:
| Exit code | Meaning |
|---|---|
0 |
The transfer succeeded (check the HTTP status separately) |
6 |
Could not resolve the host name (DNS problems, expired domain, and so on) |
7 |
Could not connect to the server (web server stopped, firewall, and so on) |
22 |
With -f (--fail), the HTTP status was 400 or higher |
28 |
Timed out |
35 |
An error occurred in the TLS (SSL) handshake |
60 |
The server certificate could not be verified (expired, misconfigured, and so on) |
Create the monitoring script
Save the following as /usr/local/bin/site-check.sh.
#!/bin/bash
# Check a site with curl and notify only when it goes down and when it recovers
set -u
URL="${1:-https://example.com/}" # URL to monitor (from the argument, otherwise this value)
EXPECT_CODE="200" # HTTP status treated as healthy
KEYWORD="" # Text the page should contain (empty = do not check)
TIMEOUT=10 # Maximum seconds for one check
RETRIES=3 # Consecutive failures before it counts as down
SLACK_WEBHOOK_URL="" # Slack Incoming Webhook URL (empty = no notification)
# File that keeps the previous result per URL (results do not mix when you monitor several URLs)
STATE_FILE="/var/tmp/site-check.$(printf '%s' "$URL" | md5sum | cut -c1-12).state"
# If the previous check is still running, do nothing this time (prevents overlapping runs for the same 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" ]
How the script works
check_onceperforms one check. It looks at thecurlexit code, the HTTP status and, ifKEYWORDis set, the text on the page, in that order, and puts the reason for any problem inREASON- On failure it waits 5 seconds and retries; after
RETRIESconsecutive failures the result is "NG". This reduces alerts caused by brief network glitches - It saves the previous result in
STATE_FILEand sends[DOWN]only on a change from OK to NG, and[RECOVERED]only on a change from NG to OK - With
flock, if the previous check of the same URL is still running, the script exits without doing anything. Even when run every minute, checks do not overlap - When the site is down the script exits with code 1, so other scripts can use the result
Set KEYWORD to text that appears on the healthy page (the site name, footer text and so on). This makes it easier to catch database connection error pages or defaced pages that still return status 200.
Sending alerts to Slack
To notify Slack, create an Incoming Webhook in Slack and put the issued URL in SLACK_WEBHOOK_URL. Anyone who knows the webhook URL can post to that channel, so make sure other users cannot read the script file.
The message is turned into JSON by wrapping the URL and the reason in ". If you monitor a URL that contains ", the JSON breaks, so you would need to change how the message is built.
Make it executable and test it
sudo chmod 700 /usr/local/bin/site-check.sh
sudo /usr/local/bin/site-check.sh https://example.com/
echo $?
If the site is healthy, nothing is printed and the exit code is 0. If you give it a page that does not exist, it prints the retry log and [DOWN] like this:
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)
If you run it again in the same state, the retry log appears but [DOWN] does not. When the URL is healthy again, [RECOVERED] is printed.
Run it every minute with cron
Open root's crontab with sudo crontab -e and add the following lines. Write one line per URL to monitor several sites.
* * * * * /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
With these settings one check can take up to about 45 seconds (a 10-second timeout × 3 attempts + 5-second waits × 2). Even if you raise TIMEOUT or RETRIES and a run goes past one minute, flock keeps checks of the same URL from overlapping.
Check the log like this:
tail -f /var/log/site-check.log
The log keeps growing, so add it to logrotate if you use the script long term.
Things to watch out for
- Where the monitor runs: if the script runs on the same server it monitors, it cannot alert you when the server itself goes down or the network is cut. Monitor from another server, or combine it with an external monitoring service
- Request frequency: requests every minute also show up in the monitored site's access log. If you want to exclude them from analytics, setting a recognizable User-Agent with
curl's-Aoption makes them easy to tell apart - Redirects: if a site redirects from
http://tohttps://and you monitor thehttp://URL, the status will be 301 or similar. Specify the final URL, or add-Ltocurlto follow the redirect - Certificates: an expired certificate is detected through exit code 60 and similar, but this script cannot warn you in advance that a certificate is about to expire
Summary
- Get the HTTP status with
curl -sS -o /dev/null -w '%{http_code}', and cap the waiting time with--connect-timeoutand-m - When the connection fails, use the
curlexit code (6, 7, 28, 60 and so on) to narrow down the cause - Retry and remember the previous state so you are notified only when the site goes down and when it recovers
- Prevent overlapping runs with
flockand run the script every minute with cron - Monitoring from the same server cannot detect a full server outage, so combine it with another server or an external service
Related articles:
- How to monitor site uptime with UptimeRobot
- How to monitor cron jobs with Healthchecks.io
- How to monitor cron jobs
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)