Short answer: separate the Node.js app health endpoint from cron job heartbeats, then judge both by signal quality rather than by how many green checks a dashboard can display. The endpoint answers whether the process can serve traffic; a heartbeat answers whether a scheduled delivery actually finished. They are different questions and deserve different alert deadlines.
At 03:00, a fintech notification service can be listening on port 3000, returning HTTP 200, and still fail to deliver a payment-expiry notice because the worker never ran. I have seen the inverse too: the job completed, but a noisy dependency alert paged the on-call before anyone knew whether customers were affected. The useful evidence is the pair of observations, their timestamps, and the delivery outcome.
How do I test the alert path before trusting it?
Treat monitoring as code and test it during deployment, not after the first customer incident. Version the endpoint contract and the heartbeat payload. In a staging window, stop the worker, return a controlled readiness failure, delay a heartbeat beyond its deadline, and simulate a provider rejection. Each action should produce one alert with a deduplicated key such as service/job/run-id, a link to raw evidence, and a clear owner. Restore the worker and verify resolution.
The external probe can be tiny and language-independent:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := http.Client{Timeout: 3 * time.Second}
started := time.Now()
resp, err := client.Get("https://notify.example.test/health/ready")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Printf("status=%d latency_ms=%d checked_at=%s\n", resp.StatusCode, time.Since(started).Milliseconds(), time.Now().UTC().Format(time.RFC3339))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic("readiness check failed")
}
}
The URL is an example owned by your service, not a vendor route. In the Node.js implementation, keep the same semantics and make the handler fast enough that a probe timeout means something. Add a correlation ID to every request, but never put customer message content into an alert title.
Measure false pages as carefully as missed pages. After each incident, ask what page fired, what evidence it carried, and whether the responder could decide within the first five minutes. Remove checks that cannot change an action. A green wall that nobody trusts is just noise with better colors.
Feature flags can help stage a new alert rule: run it in shadow mode, compare its proposed pages with the existing policy, and enable it only after the review queue shows acceptable precision. The flag must have an owner and an expiry date; otherwise an experiment quietly becomes production policy.
The practical endpoint is a small contract, a durable heartbeat record, and a tested escalation path. That combination tells you whether the service is alive, whether scheduled work ran, and whether notifications reached the boundary that matters.
The evidence has to distinguish an app health endpoint from a missed delivery.
Store raw observations before computing a status. For each endpoint check, retain probe location, checked-at time, status code, and latency. For each notification run, retain the message batch ID, tenant or ledger partition, scheduled deadline, completion timestamp, accepted count, rejected count, and a redacted error class. A status page is a view. It is not the evidence ledger.
Use a decision table like this for an incident review:
| Observation | Likely boundary | First question at the pager |
|---|---|---|
| Live fails in one region | Process, network, or deployment | Did the load balancer remove only that region? |
| Ready fails everywhere | Shared dependency or configuration | Can a bounded database check still complete? |
| Health is green, heartbeat missing | Scheduler, queue, worker, or deadline | Which run ID was expected, and where did it stop? |
| Heartbeat completes, delivery count drops | Provider response, credentials, or filtering | Are rejects isolated to one channel or tenant? |
Do not page on every rejected notification. Page on a threshold that maps to customer impact, such as a sustained increase in failed deliveries for a payment-critical channel, and route low-volume anomalies to a review queue. Your mileage may vary on the grace period; derive it from observed job duration and the business deadline, then record that choice in the runbook.
A useful test is to withhold one heartbeat in staging while the app endpoint remains healthy. The monitor should produce one identified missed-run alert, not a storm of endpoint alerts. Then send a heartbeat with a deliberately low accepted count and confirm that the delivery-quality rule, rather than the uptime rule, catches it.
The monitoring shape still needs an explicit boundary.
There is no universal best monitor. Healthchecks is designed around check-ins and missed schedules, UptimeRobot focuses on external uptime checks, and Better Uptime combines checks with incident notification workflows. Prometheus with Alertmanager is a self-managed route when the team wants metric rules and owns the storage and paging stack. These are different operating models, so compare the failure they can prove instead of counting integrations.
| Approach | Strong signal | Boundary to verify |
|---|---|---|
| Heartbeat specialist | A scheduled job did not check in | It does not prove an external client could reach the app |
| Managed uptime checker | An HTTP endpoint was reachable from a probe | It cannot infer that a queue drained or messages were accepted |
| Metrics plus Alertmanager | Rules over delivery rate, latency, and error classes | The team operates retention, routing, and silences |
| In-house event ledger | Full correlation from schedule to provider receipt | It creates pager ownership and retention work |
The catch is that a single product is not suitable when the requirement spans independent network reachability, missed schedules, and provider-level delivery evidence. Stick with a dedicated uptime service when regional reachability is the primary risk. Choose a heartbeat specialist when silent cron failure is the dominant risk. Keep a self-managed metrics stack when policy requires data residency and your team can carry its operational load.
How should a Node.js uptime health monitoring API handle cron job heartbeats?
Give the app health endpoint a narrow contract. GET /health/live should answer whether the process is alive. GET /health/ready should answer whether the instance is ready to accept work, including checks that are cheap and essential, such as a database connection pool that can complete a short query. Do not put a full third-party payment call in readiness; a slow dependency turns every deploy into an outage.
A cron heartbeat is an application event, not a probe of a port. On successful completion, write a record containing a stable job name, run ID, scheduled time, completion time, and result count. An external checker watches the expected interval and pages only when the deadline passes. A failed run should emit a failure event immediately, while a missing run should become an alert after a grace period derived from the schedule.
That distinction is the invariant: liveness protects routing, readiness protects admission, and heartbeats protect scheduled work. Combining them into one boolean loses the clue an incident responder needs at 3am.
Keep the endpoint boring.
package main
import (
"encoding/json"
"net/http"
"time"
)
type health struct {
Status string `json:"status"`
Time string `json:"time"`
}
func live(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(health{Status: "ok", Time: time.Now().UTC().Format(time.RFC3339)})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health/live", live)
_ = http.ListenAndServe(":3000", mux)
}
The production Node.js service can expose the same paths; the Go snippet is a deliberately small wire-level reference for a probe. The probe should record status code, latency, region, and response body hash, rather than trusting a screenshot.
References
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200
- https://nodejs.org/api/http.html
- https://prometheus.io/docs/alerting/latest/alertmanager/
- https://healthchecks.io/docs/
- https://uptimerobot.com/api/
- https://betterstack.com/docs/uptime/
- https://martinfowler.com/articles/feature-toggles.html
Top comments (0)