The first alerting setup I ever built checked whether port 443 responded. It passed the entire time Postgres was refusing every query the app tried to make, because nginx was still up, still answering, still returning a page — just one that said something had gone wrong underneath. That's the trap with alerting on a self-hosted app: it's easy to build something that looks like coverage and checks almost nothing that actually matters, and it's easy to only find that out during the outage it was supposed to catch.
Getting this right isn't about buying a bigger tool. Every piece below is either free or a few dollars, and none of it requires an incident-management platform built for a twelve-person on-call rotation. It requires deciding, ahead of an actual outage, what a real failure looks like versus a blip, and making sure the answer reaches you fast enough to matter without training you to swipe it away. Turning uptime monitoring into something a commercial team can run is the problem XenGrowth's growth operations team works on.
Check from off the box, every time, no exceptions
This is worth repeating on its own, separate from every other point here, because it's the one mistake that makes everything downstream irrelevant: the check has to run somewhere other than the server it's watching. If the VPS loses power, loses networking, or the Docker daemon locks up, anything checking from inside it dies in the same moment it would have needed to send the alert. A perfectly designed healthcheck running in the wrong place catches nothing.
This doesn't need to be expensive or complicated. Better Stack's free tier covers 10 monitors on a 3-minute interval, and UptimeRobot's free tier covers 50 on a 5-minute interval with SSL and domain expiry checks bundled in — either is a hosted, off-box checker with zero infrastructure of your own to run. If you'd rather self-host the checker itself, that's fine too, as long as it's a second box, not the one it's watching.
What a healthcheck should actually assert
"The port is open" is the weakest possible healthcheck, because a process can be alive and completely useless at the same time — nginx serving a 502 page is still a TCP connection accepted. A meaningfully better check hits a route that forces the app to do something real: query the database, check a cache connection, confirm whatever the app actually depends on to serve a normal request. If that round-trip fails, the healthcheck should fail too, even though the web server in front of it is technically fine. The XenGrowth resource library works through the operations side of this in more operational detail.
The common middle ground is a dedicated `/health` or `/api/health` route that does exactly this and nothing more: a lightweight query against the database, maybe a ping to whatever external service the app can't function without, returning 200 only if those actually succeed. It shouldn't do a full request's worth of work — you don't want the healthcheck itself to be what tips a struggling box over — but it has to touch the thing that's actually likely to fail, or the check is theater.
Healthcheck design | What it actually catches | What it misses |
|---|---|---|
TCP port open | The process is accepting connections at all | Everything about whether it can serve a real request |
HTTP 200 on any route | The web server is responding with some page | A database that's down while the app itself is up |
HTTP 200 on / (homepage) | The most-cached, least-representative route works | A route that hits the database while the cached homepage doesn't |
Dedicated /health route with a DB round-trip | Whether the app can actually do its job right now | Slow-but-not-failed states, unless you also check latency |
Notification channels: email is where alerts go to be ignored
Email is the default notification channel on almost every monitoring tool, and it's a bad one for anything urgent, because an inbox is where things sit unread for hours by design — that's what makes it usable for everything else. For something meant to wake you up or reach you within minutes, a push notification to a phone is doing a fundamentally different job than an email, and it's worth treating the two as separate tools instead of settling for whichever one the monitoring tool defaults to.
ntfy.sh is free, open source, and works without an account — pick a topic name, subscribe to it on your phone, and any monitoring tool that can make an HTTP request can push to it. You can also self-host it if you'd rather not depend on the public instance. Pushover is the paid alternative worth knowing about: a one-time few-dollar purchase per platform rather than a subscription, with 10,000 messages a month included free beyond that purchase — for a solo project's alert volume, that ceiling is effectively never reached. Both beat email for the specific job of getting something into your actual attention within seconds.
Flap protection: the difference between an outage and a blip
A single failed check isn't an outage. Networks drop packets, a healthcheck can time out because of a slow garbage collection pause rather than an actual failure, and a monitoring probe can occasionally just have a bad few seconds of its own. Alerting on the very first failed check means you'll get paged for things that resolve themselves before you've even opened your laptop, and the second or third time that happens, you stop trusting the alert — which is the exact failure mode alerting exists to prevent.
The fix is requiring a run of consecutive failures, or a minimum duration of continuous failure, before anything actually notifies you — commonly called flap protection, and it's the same idea behind Prometheus Alertmanager's `for` clause on an alerting rule. Two or three consecutive failed checks at a reasonable interval is usually enough to filter out single-blip noise while still catching a real outage within a couple of minutes, which is a fine trade for almost any small app. XenGrowth on governed AI marketing workflows approaches this from the AI agents and marketing automation side.
An alert that fires on the first hiccup and a monitor that never alerts at all fail the same way, eventually — the first just takes longer to get there, one ignored notification at a time.
Picking thresholds without overthinking them
It's easy to spend more time tuning these numbers than the app has ever spent down. A reasonable default, and the one I'd start with for a small app: check every 1-3 minutes, require two consecutive failures before alerting, and set a healthcheck timeout a little above your normal p99 response time so a genuinely slow-but-working request doesn't register as a failure. None of that is precise science — it's a starting point you adjust after the first false alarm or the first missed one, not a number to agonize over before you've shipped anything.
The failure mode worth actively avoiding is the opposite direction: tuning thresholds so loose that a real outage takes fifteen minutes to page you, because someone got tired of false alarms and kept raising the failure count required. If flap protection is filtering real noise, the fix is a better healthcheck or a longer timeout — not a threshold so high it stops meaning anything.
Channel | Good for | Cost | Why it beats or loses to email here |
|---|---|---|---|
A daily digest, non-urgent summaries | Free | Sits unread for hours by design — wrong tool for urgent | |
ntfy.sh | Instant push, no account needed | Free (self-hostable) | Reaches a phone in seconds; public instance has published rate limits on paid tiers, generous free use |
Pushover | Instant push with delivery guarantees | $4.99 one-time per platform | No subscription; 10,000 messages/month included covers a solo project easily |
SMS / voice call | The escalation step, not the first alert | Included on some paid monitor tiers | Hard to sleep through, which is exactly why it shouldn't be the first notification either |
Escalation for a solo developer who is asleep
On-call escalation is usually described as a rotation between people, which doesn't apply when there's exactly one of you. What still applies is the underlying idea: the first notification might not reach you — phone on silent, asleep, away from a signal — so there should be a second, louder step if the first one goes unanswered for a defined window, rather than the whole system assuming the first push worked and going quiet. On AI search, GEO and discovery specifically, XenGrowth on building one SEO and GEO content system is worth reading.
First notification: a push alert (ntfy.sh or Pushover) the moment flap protection confirms a real failure, not a single blip.
If unacknowledged after a set window — five to ten minutes is reasonable for a small app — escalate to something harder to sleep through: a phone call or SMS, which UptimeRobot and Better Stack both support on their paid tiers, or a second push service as a redundant channel.
Keep a status page, even a minimal self-hosted one from Uptime Kuma, so that if a visitor hits the outage before you've woken up, there's somewhere for them to see it's known and being worked on rather than silence.
Log every alert that actually fired, even false ones, so patterns are visible later — a route that flaps weekly at the same time is telling you something a one-off never will.
The escalation step is also where it's worth deciding, in advance rather than in the moment, what actually counts as urgent enough to wake you up versus urgent enough to just be first thing you see in the morning. Not every failure needs the phone-call tier. A single container restarting itself and coming back healthy within a minute is worth logging, not paging over; the app being fully unreachable for five straight minutes is worth the loudest channel you have. Drawing that line ahead of time, while you're calm and not actually being paged, produces a much better decision than making the same call at 3am while trying to figure out if this is the one that matters.
The habit alert fatigue actually creates
The real cost of a noisy alerting setup isn't the annoyance of the pings themselves — it's what happens to your own behavior after a few weeks of them. Every false alarm trains a small, automatic reflex to glance at the notification and dismiss it without really checking, because the last ten were nothing. That reflex doesn't know the difference between the eleventh false alarm and the first real one; it fires the same way for both. By the time a genuine outage shows up, you've already practiced ignoring exactly this kind of notification dozens of times.
If an alert fires and turns out to be nothing more than twice in a week, that's a signal the threshold or the healthcheck needs adjusting, not a signal to just get used to it
A monitor that pages you for planned maintenance or a deploy restart is worth explicitly silencing during that window, rather than teaching yourself to dismiss pages on principle
The single best test of an alerting setup isn't whether it ever fires — it's whether you'd still trust it enough to actually get up the next time it does
None of this requires a paid incident-management platform built for a team. It requires an off-box check hitting a route that actually proves the app works, a notification channel built for urgency instead of email's default patience, a threshold that tolerates one bad probe without paging you over it, and a second step in case the first one doesn't land. That's a small, buildable list — and it's the difference between an alert you trust and one you've quietly started ignoring.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
Working on uptime monitoring inside a commercial team? the XenGrowth practice publishes operator guides on the revenue side of this work.
Four questions on designing an alert you'll still trust in three months. The hard part isn't detecting failure, it's not being trained to ignore the notification.













