You do not need an observability platform to stop being blind. You need to know when the site is down, whether the database is reachable, what the error rate is, and where to look when someone reports something. That is four things, and on a small system all four fit in ten minutes. What follows is the order to do them in, because the order is what keeps it to ten minutes.
Start outside your infrastructure, because a monitor running on the box that just died tells you nothing. UptimeRobot's free tier gives you dozens of monitors at a five-minute interval; Better Stack's free tier checks more frequently and includes a status page. Either is enough.
Two configuration details matter more than the vendor. Check a URL that exercises the application, not / served from a CDN cache, or you will monitor your CDN. And require two consecutive failures, ideally from two regions, before it alerts. A single failed check from one probe is a network blip, and the fastest way to make people ignore alerts is to page them for one. If you run cron jobs or nightly batches, add a dead-man's-switch check that expects a ping, so a job that silently stops running is noticed.
Most /health endpoints return 200 as long as the process is alive, which is the one failure mode that rarely happens. A useful one touches its dependencies with a short timeout:
{
"status": "ok",
"version": "2026.8.3",
"commit": "a1b2c3d",
"checks": {
"db": { "ok": true, "ms": 4 },
"redis": { "ok": true, "ms": 1 },
"queue": { "ok": false, "ms": 2001, "error": "timeout" }
}
}
Return 503 when a required dependency fails. Split it in two if a load balancer is involved: /health/live answers "is the process wedged" and must not check dependencies, while /health/ready checks them. Conflating the two causes a database hiccup to restart-loop every instance at once. Cap each check with a two-second timeout and cache the result for a few seconds so the endpoint cannot become its own denial of service.
Netdata installs with a single command and gives you per-second host and container metrics with sensible auto-detection immediately. Grafana Cloud's free tier is the better choice if you want retention and alerting in the same place; Prometheus with node_exporter is the self-hosted route if you already run it.
Whatever you pick, resist building forty panels. Use RED for each service: request rate, error rate, and duration at p50, p95 and p99. Use USE for each host: utilisation, saturation and errors. Those five or six lines answer "is it broken, for whom, and since when" faster than a wall of gauges. Percentiles matter more than averages here, because an average latency of 120 ms hides a p99 of four seconds, and the p99 is where your complaints come from.
Pick one condition that means users are affected: the ratio of 5xx responses to total requests crossing a threshold over a rolling window, or the external uptime check failing twice. That one pages. Everything else, disk at 80 percent, a spike in a specific exception, a slow query, goes to a ticket or a chat channel and is looked at during working hours.
Alerting on CPU is the usual mistake. High CPU with healthy latency and no errors is a machine doing its job; low CPU during an outage is normal because nothing is being served. Alert on symptoms users feel, and if you have an SLO, derive the thresholds from error-budget burn rate, with a fast burn (a large fraction of the monthly budget consumed in an hour) paging and a slow burn ticketing. The test of an alerting setup is not coverage, it is whether the last ten pages were all worth waking up for.
Logs are how you answer questions you did not anticipate, which is most of them. Emit one JSON object per line with a fixed set of fields: timestamp, level, message, request_id, route, status, duration_ms, and user or tenant id. Generate the request id at the edge, propagate it through the traceparent header into downstream calls, and return it in a response header so a support ticket can quote it. Even with no log platform at all, that gives you jq 'select(.status >= 500)' app.log, which is genuinely enough for a single-server application. Do not log request bodies or headers wholesale; that is how credentials end up in a log index with ninety-day retention.
Monitoring bills scale with volume, not value, and there are two multipliers to watch. The first is log ingest: hot, indexed storage is expensive, so a sensible split is roughly a week of searchable logs plus a month or two of cheap archived storage, with 100 percent of errors kept and successful requests sampled at a small percentage. The second is metric cardinality. A label containing a user id, a request id or a full URL path turns one metric into millions of time series, and that is the invoice that surprises people, not the number of hosts. Normalise paths to route templates before they become labels. Start on free tiers, add a paid vendor when the questions you cannot answer start costing more than the tool, and keep the ten-minute stack running underneath as the thing that still works when the vendor has an outage. The first 30 days with monitoring tools covers what to add after this.