How I Get Alerted the Moment a Self-Hosted App Goes Down
Tutorial

How I Get Alerted the Moment a Self-Hosted App Goes Down

A monitor that checks the wrong thing, sends to a channel you don't watch, and pages you for every 30-second blip is worse than no monitor — it trains you to ignore it. Here's how to build alerting that actually works, for one person who eventually has to sleep.

Published December 17, 202510 min readUpdated Sep 6, 2026

Written by · Full-Stack Agentic AI Software Engineer — AI Agents, Automation & Revenue Systems for GTM/RevOps teams

In brief

How do you set up alerting for a self-hosted app so it actually catches real outages without training you to ignore it?

Four things have to be right at once: the check has to run off the box being watched, the healthcheck endpoint has to assert something real (a database round-trip, not just an open port), the notification has to land somewhere you'll actually see it fast, and the thresholds have to tolerate a single blip without paging you, while still catching a real outage quickly. Get any one of these wrong and the alerting either misses the outage that matters or trains you, through pure repetition, to stop trusting it.

  • A healthcheck that only confirms the port is open passes even when the app behind it can't reach its own database — assert something the app actually needs to work
  • Checking from off-box isn't optional — a checker sharing a failure domain with what it watches goes silent at exactly the moment it needs to alert
  • Flap protection (requiring N consecutive failures, or a 'for' duration, before alerting) is what separates a real outage from a single dropped health check under normal jitter
  • Free push channels exist for a solo developer: ntfy.sh (free, open source, self-hostable) and Pushover (a one-time few-dollar purchase, 10,000 messages a month included) both beat email for something that needs to wake you up
  • Escalation for one person isn't a rotation — it's a second, louder channel that fires if the first one goes unacknowledged for a few minutes

Evidence notes

ntfy.sh

ntfy's own site describes it as a free, open-source (Apache 2.0/GPLv2), self-hostable HTTP pub-sub push notification service usable without signup.

Pushover pricing

Pushover's own pricing page lists a one-time $4.99 purchase per platform for individuals, no subscription, with up to 10,000 messages a month included free of additional charge.

Better Stack's free monitoring tier

Better Stack's uptime page lists 10 monitors and 3-minute checks free, which is enough to run the off-box check this post argues for without any cost.

UptimeRobot's free plan

UptimeRobot's pricing page lists 50 monitors on a 5-minute interval free, with multiple notification channels including email, SMS and voice call.

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

Email

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.

  1. First notification: a push alert (ntfy.sh or Pushover) the moment flap protection confirms a real failure, not a single blip.

  2. 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.

  3. 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.

  4. 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

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.

What should actually wake you up?

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.

1 / 4
Who is realistically able to act at 3am?

Apply this article

How to turn insights into execution

A practical sequence for teams turning concepts into production outcomes.

alertinghealthchecksuptime monitoringself-hostingalert fatigueVPSon-calltutorials

Audit your current state

Map the bottlenecks and constraints connected to the article’s core problem.

Choose one bounded change

Test the most useful recommendation on one workflow before widening the scope.

Measure what changed

Keep the parts that improve the work, document what failed, and make the next decision from evidence.

Next step

Need help applying this in your stack?

I can translate these patterns into a concrete implementation plan for your team.

Discuss implementationBack to blog

Replies usually within 24 hours.

Next Steps

Continue reading

How to Self-Host Next.js With Coolify: A Complete 2026 Walkthrough

Every Coolify tutorial stops at "push to deploy." This one covers the parts that actually break a real app: standalone output, a build that OOMs on a small box, env vars baked in at the wrong time, and a health check that would have caught all of it.

Navigate

How to Host Unlimited Domains on One VPS With Free SSL

"Unlimited domains, one server" sounds like a sales pitch, but it's a genuinely accurate description of what SNI-based virtual hosting and Let's Encrypt automation do together. The real ceiling isn't domain count. It's RAM.

Navigate

The Docker Concepts You Need Before Coolify Hides Them From You

Coolify's whole pitch is that you shouldn't have to think about Docker, and most days that's true. The days it isn't are the days a container is 'running' but unhealthy, a redeploy quietly ate a volume, or an env var vanished — and the dashboard doesn't explain any of it.

Navigate

How to Back Up and Restore a Coolify Server Properly

Coolify's built-in backup covers less than most people assume, and a filesystem copy of a live Postgres data directory is not a valid database backup no matter how confident it looks in a file listing. Here's what actually has to be backed up, and a restore you've actually tried before you need it.

Navigate

How I Restore an Entire Server From Backup (And How Long It Takes)

The order matters more than people expect, and the answer to "how long will this take" isn't a number I can hand you — it's dominated by your database size, your download bandwidth, and one DNS setting most people only think to change after they needed to.

Navigate

How I Back Up PostgreSQL to S3-Compatible Storage on a Schedule

pg_dump, pg_basebackup and WAL archiving aren't three ways to do the same thing — they answer three different questions about how much data you're willing to lose. Here's which one to run, from a container that has no business having shell access to your host, pushed to storage that makes restoring free instead of expensive.

Navigate

Docker Compose Is More Powerful Than You Think

Most people learn Compose from a five-line docker-compose.yml in a README and stop there. Profiles, real healthchecks, resource limits, and layered override files cover most of what people reach for Kubernetes to get — you're probably one flag away from using the tool you already have.

Navigate

Wildcard Domains With Coolify and Cloudflare

A wildcard certificate can't be proven the way a normal one is — there's no single page to fetch for a domain that doesn't exist yet. That's why it needs a DNS record instead of an HTTP request, and why Cloudflare's API has to be involved at all.

Navigate

Setting Up a Firewall for Self-Hosted Apps (and Docker's Nasty UFW Surprise)

ufw status can say everything's locked down while a container you published with -p 5432:5432 sits wide open to the internet. This isn't a misconfiguration — it's Docker rewriting your firewall's decisions before ufw ever gets a vote, and the only way to know for sure is to check from a machine that isn't the one you're worried about.

Navigate

Where console.log Actually Goes When You Self-Host Next.js

On Vercel, a console.log just shows up in a dashboard somewhere. Self-hosted, it goes through a chain most people never trace end to end — and the two most common questions I get are why a log doesn't show up at all, and why the disk filled with logs nobody remembers writing.

Navigate