Without monitoring, you find out about server problems from unhappy customers. Uptime monitoring tools check availability every 1–5 minutes and instantly alert you to outages.
Free tools
| Service | Free plan | Interval |
|---|---|---|
| UptimeRobot | 50 monitors | 5 min |
| Hetrix Tools | 15 monitors | 3 min |
| Better Uptime | 10 monitors | 3 min |
| StatusCake | 10 monitors | 5 min |
UptimeRobot: 5-minute setup
- Sign up at uptimerobot.com
- Add New Monitor → HTTP(s)
- URL: your site or IP
- Monitoring Interval: 5 minutes
- Alert Contacts: add email and/or Telegram
Local monitoring via script
#!/bin/bash
# /usr/local/bin/check-services.sh
services=("nginx" "mysql" "php8.2-fpm")
BOT_TOKEN="your_telegram_token"
CHAT_ID="your_chat_id"
for service in "${services[@]}"; do
if ! systemctl is-active --quiet "$service"; then
msg="ALERT: $service is DOWN on $(hostname)!"
curl -s "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" -d "chat_id=$CHAT_ID&text=$msg" > /dev/null
systemctl restart "$service"
fi
done
# Add to cron (every 2 minutes)
*/2 * * * * /usr/local/bin/check-services.sh
Resource monitoring
# Netdata — a beautiful real-time dashboard
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
# After installation: http://your-vps-ip:19999
sudo ufw allow from YOUR_IP to any port 19999Common monitoring errors
UptimeRobot shows Up, but the site returns a 500 error — an external monitor checks only the HTTP status code by default. If the server returns "200 OK" with an empty error page inside (for example, a PHP exception was caught and a fallback page was shown), the monitor will not notice it.
# Check the response code manually
curl -o /dev/null -s -w "%{http_code}\n" https://example.com
200
# But the content inside is a PHP error
curl -s https://example.com | grep -i "fatal error"
Fatal error: Uncaught PDOException in /var/www/app/db.php:12
The fix is to set up Keyword Monitoring in UptimeRobot: specify a text string that must be present on the page during normal operation, and the monitor will mark the site as Down when that text is missing.
The local script restarts the service, but no Telegram alert arrives — the most common cause is incorrect escaping of the ampersand in the URL, or stray spaces from a line continuation with \.
curl: (3) URL using bad/illegal format or missing URL
This means the variable $msg contains spaces and is not quoted, or the line break after \ added an invisible character. Check the script line by line with bash -x check-services.sh.
Comparing monitoring methods
| Method | What it checks | Detection delay | Needs server access |
|---|---|---|---|
| UptimeRobot / external service | Site availability from outside | 1-5 minutes | No |
| Local script + cron | Status of specific systemd services | 1-2 minutes | Yes |
| Netdata | CPU, RAM, disk, network in real time | Seconds (but local) | Yes |
| Grafana + Prometheus | Metrics plus threshold alerts | 15-60 seconds | Yes |
When external monitoring is not enough
- You need to know the cause of an outage, not just the fact of it — an external monitor says "site unreachable" but will not show that memory ran out or disk is at 100%.
- Reaction speed before user complaints matters — a 3-5 minute interval on an external service means some users already saw the error.
- The service runs only on an internal network with no public IP — UptimeRobot and similar tools cannot reach a private address.
- You need long-term metrics for trend analysis — free external monitors usually keep history for no more than 1-3 months.
- You need to watch more than the web server — background jobs like cron or queues — an external HTTP monitor cannot see a hung queue worker; that needs separate heartbeat checks from inside the application itself.
Verifying the result
- In the UptimeRobot dashboard the monitor status must switch to
Upwithin the first check cycle after setup. - Stop Nginx manually (
sudo systemctl stop nginx) and confirm the down alert arrives within the stated interval. - Start Nginx again and confirm a recovery alert (Up) arrives, not just a down alert.
- For the local script, check the log:
tail -20 /var/log/syslog | grep check-services— it must contain entries showing the cron job ran.
For deeper resource analysis after an alert, see Netdata, and for metrics with history and graphs, see Grafana with Prometheus.