The default monitoring on most VPS plans is a five-minute ping and a CPU graph. That interval is fine for capacity trending and useless for the failures that cost money: a web process returning 502s, a disk filled to 100%, a database that has begun swapping. All three complete in under a minute — fully developed before a five-minute check even runs.
Getting detection under 60 seconds is mostly a design problem, not a cost problem. A 10-second scrape of five to eight local signals costs under 1% of one vCPU and a few megabytes of disk a day. This covers which signals justify that interval, how to avoid alert storms, and what retention costs. The stack runs on the VPS itself, so the plan’s memory and disk limits matter — compare them on the provider comparison list before sizing the agent.
What Actually Fails in Under a Minute
Slow degradations — memory creeping up, disk filling over days — are exactly what five-minute checks were designed for. Instant failures are a different class: the process dies and the socket closes, the disk hits ENOSPC on a log write, the OOM killer terminates the database, the swap file starts thrashing. Those need to be caught in the first ten to thirty seconds, because a database that pages for sixty seconds may take ten minutes to recover its warm cache.
The other category is the slow failure that a five-minute check smears across intervals: a service returning errors for 45 seconds out of every 300. Averages hide it completely. Interval-based counting does not.
The Eight Signals Worth Ten-Second Scrapes
More metrics is not better: each signal is another threshold to tune and more false pages. These eight cover what a VPS fails at, and all but the first read from /proc or /sys without touching the monitored app.
- HTTP success rate — a one-line probe of a real endpoint that touches the database, not a static file
- Process liveness — PID exists and has not been replaced by a new PID since the last check
- Load average over 1 minute — the only load figure that responds fast enough to matter
- Memory available — from
/proc/meminfo, not “free” - Swap-in rate —
pswpindelta per second, the clearest early sign of memory pressure - Disk space and inode usage — both, because a mail queue can exhaust inodes at 20% space used
- Disk latency p95 — from
/proc/diskstats, not IOPS - Conntrack and socket counts — the two tables that silently fill and then drop traffic
A static file probe is the most common mistake. It returns 200 while the application pool is exhausted, because the web server answers from its own cache. Probe an endpoint that performs one query and returns a row.
Polling Intervals and Their Cost
| Interval | Worst-case detection | Daily samples (8 metrics) | CPU cost |
|---|---|---|---|
| 60 s | 60-120 s | 11,520 | under 0.2% |
| 30 s | 30-60 s | 23,040 | 0.2-0.5% |
| 10 s | 10-20 s | 69,120 | 0.5-1.5% |
| 5 s | 5-10 s | 138,240 | 1.5-3% |
| 1 s | 1-2 s | 691,200 | 8-15% |
Five seconds to one second is where monitoring stops being free. At one-second resolution the collector’s own wakeups and the write amplification of a time-series store both start to matter, and on a 1-vCPU guest that is real contention. Ten seconds is the sweet spot for most services; the table’s worst-case detection is twice the interval because an alert usually requires two consecutive failed checks to avoid flapping.
Reading the Counters Without a Collector
A full Prometheus deployment is not required for ten-second detection. A shell loop that samples /proc and posts to a webhook when a threshold trips is 40 lines, has no memory footprint, and cannot itself run out of memory. That matters: an agent killed by the OOM killer during a memory event is the least helpful software in the world.
while :; do
avail=$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo)
swapin=$(awk '/pswpin/ {print $2}' /proc/vmstat)
inodes=$(df -i / | awk 'NR==2 {print $5}' | tr -d %)
if [ "$avail" -lt 100 ] || [ "${inodes%\%}" -gt 85 ]; then
curl -s -X POST -d "mem=${avail}MB inodes=${inodes}" $HOOK
fi
sleep 10
done
Two details make this usable rather than noisy. Rate-limit notifications to one per five minutes per condition, and put the value in the message so whoever wakes up knows whether it is 99 MB free or 4 MB. Alert text without a number gets ignored by the second page.
Thresholds That Do Not Storm
The default thresholds shipped with monitoring systems are almost always wrong for a small VPS. A load average above 2 is normal on a 4-vCPU guest running a build; it is fatal on a 1-vCPU guest. Memory available below 200 MB is fine when it is page cache and the application can reclaim it, which is why MemAvailable and not MemFree is the correct field.
Set two levels per signal: a warning that goes into a chat channel and an alert that pages. The warning fires first and often; the page fires rarely enough that people still respond to it. More than two or three actionable pages a week means the thresholds are too tight and everyone will start ignoring them.
| Signal | Warning | Page | Sustained |
|---|---|---|---|
| HTTP success | < 99% | < 95% | 20 s |
| MemAvailable | < 15% of RAM | < 5% of RAM | 30 s |
| pswpin rate | > 50/s | > 500/s | 30 s |
| Disk used | > 85% | > 95% | immediate |
| Inodes used | > 80% | > 90% | immediate |
| Load /vCPU | > 1.5 | > 3.0 | 60 s |
Storing Ten-Second Data Without Filling the Disk
Eight metrics at ten-second resolution is 69,120 samples a day, As one timestamped line each that is about 2 MB a day, or 60 MB a month — nothing. Stored badly, with a JSON document per scrape, it is 50 times that and the monitoring system becomes why the disk fills.
Append one line per sample, rotate by size, downsample after a week. Ten-second data is useful for a few days; after that a one-minute average answers every question anyone asks, and keeping raw samples for a year is a storage decision with no diagnostic value.
# one line per scrape, then daily rollup
printf '%s %s %s %s\n' "$(date +%s)" "$avail" "$swapin" "$inodes" >> /var/log/probe.log
logrotate -s /var/lib/logrotate.status /etc/logrotate.d/probe
# 1-minute means from 10s samples
awk '{k=int($1/60); s[k]+=$2; n[k]++} END {for (i in s) print i*60, s[i]/n[i]}' /var/log/probe.log
Test the Alert Path, Not Just the Alert
The failure that matters is the notification, not the metric. A probe that detects a dead process at ten seconds and then sends its webhook to a host that is also down has detected nothing. Route alerts through an external endpoint and test the path monthly by stopping a service deliberately and timing the message.
Time detection and delivery together. If detection is 15 seconds and delivery takes 4 minutes because of a mail queue, the interval you tuned was never the bottleneck. That end-to-end number — under 60 seconds, measured, not assumed — is the actual target, and it is achievable on a modest plan: check the VPS feature breakdown for the memory and disk an agent plus a year of retention actually needs.



Leave a Reply
You must be logged in to post a comment.