Monitor Node.js scripts: one fetch call, zero dependencies
Node 18+ ships fetch built in, so the whole integration
is: ping on success, POST the error to /fail on failure.
Absence detection covers what your catch can't: the OOM
kill, the crashed process, the cron entry that vanished. Free, no
monitor cap.
Your assistant can do this for you
Monitor my Node.js scripts with LastPing
On this page
The snippets
Create a monitor in the console (its schedule = how often the job should run, plus a grace window), copy the ping URL, and wrap your entry point.
ESM (top-level await)
// job.mjs — Node 18+ (built-in fetch, no dependencies)
const PING = "https://ping.lastping.dev/<your-monitor-id>";
async function main() {
// your job
}
try {
await main();
await fetch(PING);
} catch (err) {
// report the failure; the body is stored with the ping (10,000 bytes max)
await fetch(`${PING}/fail`, {
method: "POST",
body: String(err && err.stack ? err.stack : err).slice(0, 10000),
});
process.exitCode = 1;
}
CommonJS
// job.js — CommonJS variant
const PING = "https://ping.lastping.dev/<your-monitor-id>";
async function main() {
// your job
}
main()
.then(() => fetch(PING))
.catch((err) =>
fetch(`${PING}/fail`, {
method: "POST",
body: String(err).slice(0, 10000),
}).finally(() => {
process.exitCode = 1;
})
);
Optional: overrun detection and run duration
A /start ping arms the hung-job alarm (no success
inside the grace window → incident), and a shared
rid pairs start with finish so each run's duration
is recorded:
const PING = "https://ping.lastping.dev/<your-monitor-id>";
const rid = Date.now().toString();
await fetch(`${PING}/start?rid=${rid}`);
try {
await main();
await fetch(`${PING}?rid=${rid}`);
} catch (err) {
await fetch(`${PING}/fail`, {
method: "POST",
body: String(err).slice(0, 10000),
});
process.exitCode = 1;
}
Why process.exitCode = 1 matters
The non-zero exit keeps the failure visible to whatever runs the
script (cron,
systemd, a
Kubernetes CronJob) so their
retry semantics still apply. Using process.exitCode
instead of process.exit() lets the fail ping finish
sending first.
What this catches
The catch reports what it can; the silence reports the
rest.
- A thrown error: the
/failping opens an incident immediately, with the stack trace stored on the ping. - A crash your code never sees: OOM kill, unhandled hard crash,
kill -9. No success ping arrives; the grace window raises the alert. - The job never ran: missing cron entry, broken deploy, dead host. Absence → incident. This also covers in-process schedulers like node-cron: if the whole process dies, the scheduler dies with it, and only an external monitor notices.
- A hung run: with the
/startping armed, a job stuck on a dead connection opens an incident inside the grace window. - Recovery: the next successful run closes the incident and sends a recovery notice automatically.
Questions people ask
Front-loaded answers: the most important fact first.
-
How do I monitor a Node.js cron job?
Wrap the entry point: on success, fetch the monitor URL
https://ping.lastping.dev/<your-monitor-id>; on error, POST the error text to the/failendpoint and setprocess.exitCode = 1. If the script fails, hangs, or never starts, LastPing alerts you by email, Slack, Discord, Telegram, ntfy, Pushover, Microsoft Teams, Google Chat or webhook. -
Do I need an npm package to do this?
No. Node 18 and later ship
fetchbuilt in, so the integration is zero-dependency. Any HTTP client works: the ping accepts GET, HEAD, or POST. -
What if the process crashes before it can ping?
A crashed or OOM-killed process sends no ping, and that missing ping is itself the alert. LastPing expects a signal on your schedule plus a grace window; silence opens an incident, so the failure path requires no working code.
-
Does this work with node-cron or BullMQ-style schedulers?
Yes. Wrap the scheduled task body in the same try/catch pattern. The external monitor also covers the failure the in-process scheduler can't see: the whole Node process dying and taking the scheduler with it.
-
Is Node.js job monitoring free?
Yes. LastPing is free for individuals with no monitor cap. See how it compares to Healthchecks.io and Cronitor.
A dead process can't reject a promise.
One fetch call now beats debugging an empty table later. First alert wired up in about a minute, and LastPing monitors itself the same way.