Monitor Go cron jobs: defer ping on exit, stdlib net/http only
A named-return defer inspects the error value at function
exit and either GETs the success URL or POSTs the error text to
/fail. Add recover() inside the same defer to
turn panics into /fail pings. No external packages.
Absence detection handles what defer can't: the OOM kill, the crashed
binary, the cron entry that vanished. Free, no monitor cap.
The snippets
Create a monitor in the console, set the schedule to match how often the binary runs plus a grace window, copy the ping URL, and wrap your job function.
Named-return defer — stdlib only
Declaring err error as a named return lets the
deferred function read the final value after the function body has
run, including deferred returns from inner functions:
In-process scheduler with gocron
When using cron-style in-process
scheduling via github.com/go-co-op/gocron, wrap the
task body in the same pattern. The external monitor also covers
the case where the whole process dies and takes the scheduler with
it:
Optional: /start ping for overrun detection
Ping /start before the job runs to arm the hung-job
alarm — if no success arrives within the grace window the incident
opens even if the process never errors:
Why re-raise (os.Exit) still matters
Returning a non-nil error from runJob and calling
os.Exit(1) in main keeps the exit code
non-zero so cron,
systemd, and container orchestrators
still apply their own retry and alerting semantics. The ping adds
fast alerting; it doesn't replace the error propagation chain.
Also see the LastPing MCP server for creating
monitors programmatically from AI agents.
What this catches
The defer reports what it can; the silence reports the rest.
- A returned error — the
/failping opens an incident immediately, with the error text stored on the ping event. - A panic —
recover()inside the defer converts the panic value to an error and sends the/failping before the goroutine terminates. - An OOM kill or SIGKILL — the process sends no ping; silence after the grace window opens the incident.
- The binary never ran — missing cron entry, broken deploy, dead host. Absence → incident.
- A hung run — with the
/startping armed, a job stuck on a blocked channel or a dead connection opens an incident inside the grace window.
Questions people ask
Front-loaded answers — the most important fact first.
-
Do I need a third-party package?
No. The stdlib
net/httppackage handles both GET and POST requests. You only need an external scheduler likegocronif you want in-process scheduling; the ping call itself is always stdlib. -
How do I catch panics and send a /fail ping?
Add
recover()inside the same deferred function. Ifrecover()returns a non-nil value, assign it to the named return error. The rest of the defer logic then picks up the non-nil error and POSTs to/failautomatically. -
What if the Go binary is OOM-killed?
Deferred functions do not run when the process is killed with SIGKILL. That is exactly why absence-detection monitoring exists: the missing ping itself opens the incident after the grace window, no working code required.
-
Does this work with a Kubernetes CronJob?
Yes — the Go binary runs as the container entrypoint. The non-zero exit from
os.Exit(1)causes the CronJob to mark the run as failed and apply its ownfailedJobsHistorypolicy. See also the Kubernetes CronJob monitoring guide. -
Is this free?
Yes. LastPing is free and fully hosted with no monitor cap and no paid tier. See how it compares to Healthchecks.io and Cronitor.
Deferred functions don't run when the kernel kills your process.
One defer now beats debugging a silent data gap later. First alert wired up in about a minute — and LastPing monitors itself the same way.