You find out a server is down from a customer email, not from your own tooling. That’s the moment most people start looking into linux server uptime alerts without third party monitoring, because the obvious fix, a paid monitoring subscription, feels like overkill for one box that occasionally falls over. It usually is overkill. You don’t need a vendor dashboard and a monthly invoice to know when a host stops responding, you need a check that runs on a schedule and a way to get pinged when it fails.
This guide walks through the actual DIY options, from a five-line bash script to a self-hosted tool, and where each one stops being enough. As covered in our complete guide to Linux server monitoring, uptime is only one signal among several, but it’s the one that pages you the fastest when it’s missing.
How do you monitor Linux server uptime without a third-party monitoring bill?
There are four real tiers, roughly in order of effort:
- A script you write yourself, run on a schedule with cron or a systemd timer, that alerts you on failure.
- A dead man’s switch: a free hosted endpoint that alerts you if your server stops checking in, rather than the other way around.
- A small self-hosted tool like Monit or Uptime Kuma, which gives you a dashboard and history at the cost of one more service to run and patch.
- Alerts built into a tool you already run against your servers, so there’s no separate stack to maintain at all.
None of these involve a subscription. The tradeoff isn’t cost, it’s how much of the plumbing you’re willing to own. A lot of the top-ranking advice on this topic jumps straight to “self-host Uptime Kuma in Docker,” which is genuinely good advice, but it skips over the fact that Uptime Kuma is itself a service that needs a VPS, updates, and backups. If the goal is fewer things to babysit, it’s worth knowing the tiers below it first.
What’s the simplest heartbeat check you can write yourself?
A heartbeat check is just: try to reach the host, and do something if you can’t. Two attempts in a row failing is usually a better trigger than one, since a single dropped packet on a bad network hop is common and not worth waking up for.
A bash script that pings and curls your host
#!/usr/bin/env bash
set -euo pipefail
HOST="203.0.113.10"
WEBHOOK_URL="https://hooks.example.com/alert"
FAILURES_FILE="/tmp/heartbeat-failures"
if ping -c 2 -W 3 "$HOST" > /dev/null 2>&1; then
rm -f "$FAILURES_FILE"
exit 0
fi
COUNT=$(( $(cat "$FAILURES_FILE" 2>/dev/null || echo 0) + 1 ))
echo "$COUNT" > "$FAILURES_FILE"
if [ "$COUNT" -ge 2 ]; then
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"$HOST has failed $COUNT consecutive checks\"}"
fi
This runs from a separate machine, not the server you’re checking, otherwise a full outage never gets a chance to report itself. A cheap always-on box, a home server, or even a laptop that’s usually on is enough.
Scheduling it with cron
*/2 * * * * /usr/local/bin/heartbeat.sh >> /var/log/heartbeat.log 2>&1
That runs the check every two minutes. Redirect output to a log file, because cron jobs fail silently by default: a broken script just stops producing output, it doesn’t send you anything.
Cron or systemd timers: which is more reliable for uptime alerts?
Cron is fine for a single, simple job. It has two real weaknesses for anything you’re depending on: it doesn’t tell you when a job fails to run at all (a typo’d path just never executes, no error), and its environment is minimal, so a script that works in your shell can fail under cron with a different PATH. systemd timers fix both: failures show up in systemctl status and journalctl, and the unit runs with an explicit, predictable environment.
Writing a systemd service and timer pair
[Unit]
Description=Heartbeat check for production host
[Service]
Type=oneshot
ExecStart=/usr/local/bin/heartbeat.sh
[Unit]
Description=Run heartbeat check every 2 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=2min
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now heartbeat.timer
If the timer itself ever stops firing, that’s visible with systemctl list-timers, which is the kind of failure a plain crontab hides from you entirely.
How do you get notified the moment a check fails?
A check that fails silently into a log file isn’t an alert, it’s a record you’ll read after the fact. Pick one delivery method and make sure it doesn’t depend on the same infrastructure you’re monitoring.
Email alerts via msmtp or sendmail
sudo apt install msmtp msmtp-mta
# ~/.msmtprc
account default
host smtp.example.com
port 587
auth on
user alerts@example.com
password yourpassword
tls on
from alerts@example.com
echo "Subject: host down\n\n$HOST failed $COUNT checks" | msmtp your@email.com
Email is slow but reliable, and works even if your phone’s on silent for everything except email notifications. It’s a reasonable default if you check email more often than you check a dashboard.
Webhook alerts to Slack, Discord, or Telegram
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"text": "'"$HOST"' is not responding"}'
Webhooks land in a channel you’re already watching, and Telegram bot alerts show up as push notifications on your phone within seconds, which makes them a better fit than email if you want to actually be woken up.
Is a dead man’s switch like healthchecks.io worth using?
Every setup above has one blind spot: what happens when the checking machine itself goes down, or the cron job stops running? Nothing alerts you, because the thing that was supposed to alert you is gone. A dead man’s switch flips the direction: instead of your script pinging out to confirm a failure, your script pings in to confirm it’s alive, and the switch alerts you if that ping stops arriving.
curl -fsS -m 10 --retry 3 https://hc-ping.com/your-unique-check-id > /dev/null
Services like healthchecks.io offer a free tier that covers a handful of checks, which is plenty for a couple of personal servers. It’s a small addition on top of the cron or systemd setup above, not a replacement for it, and it’s the piece most DIY monitoring setups skip.
When does a lightweight tool like Monit or Uptime Kuma make more sense than raw scripts?
Raw scripts scale fine up to a handful of checks on a handful of hosts. Past that, a small dashboard starts paying for itself.
- Monit runs as a lightweight daemon, checks processes and hosts on an interval, and can restart a crashed service automatically, not just alert about it.
- Uptime Kuma adds a web dashboard, status history, and built-in notification integrations for a dozen or more services, at the cost of running it in Docker somewhere.
- Both are one more thing to patch and back up. If you’re already running a VPS you trust for other workloads, that cost is small. If this would be the first extra service you host, raw scripts plus a dead man’s switch get you most of the same coverage for less upkeep.
What do you actually give up by skipping a full monitoring stack?
Be honest about the tradeoff before committing to the DIY route. You give up long-term history and graphs (was last Tuesday’s blip an anomaly or a pattern?), multi-region checks (a check from one location can’t tell you if the outage is regional), and structured on-call escalation (paging a second person if the first doesn’t acknowledge). None of that matters for one or two personal or small-team servers. It starts to matter once uptime has a real business cost attached to it, and at that point paying for a proper monitoring service, or dedicating time to Prometheus and Grafana, is the right call, not a failure of the DIY approach.
Uptime is one piece of the picture. Pair it with the resource-level checks in disk usage monitoring best practices for small ops teams and our guide on how to monitor CPU, RAM, and disk without third-party tools, so a full disk or a maxed-out CPU doesn’t quietly take the host down before your heartbeat check ever notices.
How termique’s built-in alerts fit into a no-subscription setup
If you’re already connecting to these hosts over SSH day to day, there’s a version of this that skips the extra script and the extra service entirely. termique, the SSH manager we build, watches CPU, RAM, disk, and uptime on every host you connect to, live, with custom alert thresholds you set per host, and pushes a notification to your phone the moment a server goes offline or crosses a threshold. It’s free on every plan, it’s not a separate stack to run or patch, and it’s already open on the same machine you’d use to SSH in and investigate the alert anyway.
For a single server or two, that’s often the whole answer: no cron job, no webhook config, no dashboard to host. For anything bigger, treat it as one more layer alongside the scripts and dead man’s switch above, not a replacement for either.
Whichever tier you land on, the goal is the same: hear about an outage from your own tooling before you hear about it from a customer, without signing up for another monthly bill to get there. Start with a heartbeat script and a dead man’s switch, add a self-hosted dashboard once you have enough hosts to justify it, and keep the alert path (email, webhook, or push notification) as short and boring as possible, since the whole point is that it works on the one day you’re not staring at a terminal.