You know your app is down by watching it from two sides. From outside, an uptime check hits a health endpoint on a schedule and alerts you when it stops responding. From inside, you alert on error rate and whether key user actions still complete. "Down" has two meanings, fully offline and up-but-not-working, and the trap is catching only the first. Here is how to catch both, before your users tell you.
The two meanings of "down"
"My app is down" covers two different failures, and they need different detection.
- Fully offline. The server is gone, a deploy broke the build, DNS is misconfigured, the region is out. Nothing responds. This is the obvious kind, and the easiest to detect.
- Up but not working. The app responds, returns a cheerful 200, and is useless: it cannot reach the database, a dependency is down, or the one action users came for silently fails. This is the dangerous kind, because a naive uptime check that only asks "did I get a response" says everything is fine.
This distinction is old enough to be built into infrastructure. Kubernetes separates a liveness check ("is the container alive", restart it if not) from a readiness check ("can it serve traffic right now", take it out of rotation if not), precisely because an app can be alive and still unable to serve.1 You want to detect both states, and the cost of finding out late is real: ITIC's 2024 survey found more than 90% of mid-size and large firms lose over $300,000 for a single hour of downtime.2 For a small app the currency is users and trust, but the logic is the same, you cannot afford to learn about it from a customer.
Health checks: the basic signal
The building block is a health check: a dedicated route, usually /health or /healthz, that something can call to ask "are you okay". The important choice is how deep it goes.
A shallow health check returns success if the process is running. That catches fully offline, and nothing else. A deeper readiness check returns success only if the app can actually do its job right now: it can reach the database, its critical dependencies respond, it is not mid-restart. That is the check that catches up-but-broken, because when the database connection dies, a readiness endpoint starts failing while a shallow one keeps happily returning 200. Make your health endpoint check the things a request actually needs, not just that the server booted.
Watch from outside, not just inside
Here is the part people miss. Monitoring that runs inside your app is great for "is it working", but it cannot tell you the app is completely down, because if the whole server or region is gone, your internal monitoring went down with it. A dead app cannot send you an alert saying it is dead.
So you need an external check: something outside your infrastructure that hits your health endpoint on a schedule, every 30 or 60 seconds, from somewhere your app is not. When it gets no response, or the wrong one, several times in a row, it pages you. That is the only way to reliably catch a total outage. The full picture is both directions at once: an external uptime check for "is it reachable at all", and internal signals for "is it actually working". Neither alone is enough.
What to actually alert on
Put together, a small set of signals catches both meanings of down without drowning you in noise.
| Signal | Which "down" it catches | Alert when |
|---|---|---|
| External health check | Fully offline | No response, or a bad one, several times in a row |
| Error rate | Up but erroring | Errors spike above the route's normal level |
| Key action completion | Up but broken | Signup or checkout stops completing at its usual rate |
| Latency | Degrading toward down | p95 blows past the route's baseline |
These are the golden signals of monitoring, latency, traffic, errors, and saturation, applied to the one question of availability.3 If you want a target to hold yourself to, that is an availability objective: pick a number like 99.9% uptime and alert when you are burning through the small budget of downtime it allows, an idea Google's site reliability engineering practice formalizes as service level objectives.4 For a first app, though, the four alerts above are plenty. Keep it small, as we argue in what to monitor when you launch your first app.
Knowing before your users do
Standing up an external uptime monitor, health checks, and internal alerts separately is a small project on its own. Fixter does it from one place. You drop in the SDK, and you define what "up" means for your app in plain language: reachable, error rate normal, and the actions that matter actually completing. It watches from outside and inside, and when the app goes down in either sense, you get a message that says what kind of down it is and what broke, not just a red dot.
The goal is simple: never learn your app is down from a customer. For the wider set of things worth watching once the basics are in place, see what you can do with your logs, and when it is up but slow, why is my app slow.
Key takeaways
- "Down" means two things: fully offline, and up-but-not-working; catch both
- Use a deep health check that fails when the app cannot reach its database or dependencies, not just when the process dies
- An external uptime check is the only reliable way to catch a total outage; internal monitoring dies with the app
- Alert on the health check, error rate, key-action completion, and latency
- Fixter watches from outside and inside and tells you which kind of down it is
Frequently asked questions
How do I know when my app goes down?
Watch it two ways. From outside, an uptime monitor hits a health endpoint on a schedule and alerts you when it stops responding. From inside, you alert on error rate and whether key user actions still complete. The outside check catches a fully offline app; the inside checks catch an app that is up but not actually working.
What is a health check endpoint?
A health check is a route, often /health or /healthz, that returns a success status only when the app can actually do its job, for example when it can reach its database. A shallow check just confirms the process is alive; a deeper readiness check confirms it can serve real traffic, which is the one that catches up-but-broken.
What is the difference between liveness and readiness?
A liveness check asks 'is the app alive', and if it fails the usual response is to restart it. A readiness check asks 'can it serve traffic right now', and if it fails the app is taken out of rotation until it recovers. Kubernetes formalizes both; the distinction matters because an app can be alive but unable to serve.
Do I need an external uptime monitor if I already have internal monitoring?
Yes. Internal monitoring runs inside your app, so if the whole server, region, or DNS is down, it goes down with it and cannot alert you. An external check hits your app from outside on a schedule, which is the only way to catch a total outage. Use both: external for 'is it reachable', internal for 'is it working'.
What should trigger a 'my app is down' alert?
Four things: the external health check failing (offline), a spike in error rate (up but erroring), a key action like signup or checkout no longer completing at its normal rate (up but broken), and latency blowing past its baseline. Together they cover both meanings of down without paging you for noise.
Sources
- Kubernetes, Liveness, Readiness and Startup Probes (alive vs able to serve traffic)
- ITIC, 2024 Hourly Cost of Downtime Survey (90%+ of mid-size and large firms lose over $300,000 per hour of downtime)
- Google, Site Reliability Engineering, the four golden signals (latency, traffic, errors, saturation)
- Google, Site Reliability Engineering, Service Level Objectives (availability targets and the downtime budget they allow)