Monitor .NET background jobs: BackgroundService, Hangfire, and Quartz.NET
For a BackgroundService: wrap the PeriodicTimer
tick body in try/catch, await _http.GetAsync on success,
await _http.PostAsync to /fail on exception.
For Hangfire: register a global IServerFilter so every
recurring job is covered without touching individual job classes.
For Quartz.NET: implement IJobListener and check
context.JobException. No NuGet packages needed beyond what
you already have. Free, no monitor cap.
The snippets
Create a monitor in
the console, set the
schedule plus a grace window, and add the ping URL to
appsettings.json.
BackgroundService with PeriodicTimer
PeriodicTimer (.NET 6+) avoids timer drift.
IHttpClientFactory manages connection pooling.
The exception is logged and re-thrown as
OperationCanceledException is not re-thrown to keep
the service running:
Hangfire — global IServerFilter
Registering a global filter covers every recurring job without
modifying individual job classes. Register it in
Program.cs via
GlobalJobFilters.Filters.Add:
Quartz.NET — IJobListener
Implement IJobListener and register it on your
scheduler. JobWasExecuted is called after every job
run; check jobException to decide which ping to send:
One ping URL per job vs. global filter
A single ping URL works for a single job. For multiple jobs each
needing its own monitor, pass the URL as job data (Hangfire:
PerformContext.GetJobParameter; Quartz.NET:
context.JobDetail.JobDataMap). The
LastPing MCP server lets AI agents create and
manage monitors programmatically. Also see the
cron job guide for the OS-scheduler
approach and the Java guide for
Spring equivalents.
What this catches
The catch and listener report what they can; the silence reports the rest.
- A thrown exception — the
/failping opens an incident immediately with the exception message stored. - Hangfire job failure —
OnPerformedfires with a non-null exception; the global filter POSTs to/failwithout touching job classes. - Quartz.NET job exception —
JobWasExecutedwith a non-nulljobExceptiontriggers the/failping. - Process kill / OOM — no ping fires; the missing ping opens the incident after 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.
-
Do I need a NuGet package?
No.
System.Net.Http.HttpClienthandles GET and POST. RegisterIHttpClientFactoryin your DI container for connection pool management in long-running services. -
How do I cover multiple Hangfire jobs with different monitors?
Store the ping URL in the job's
JobDataMapor as a job parameter. In the global filter, read it fromcontext.GetJobParameter<string>("pingUrl")and use it instead of the constant. -
What if the worker process is killed?
The filter or listener never runs for a SIGKILL — no ping reaches LastPing. Absence-detection monitoring handles this: the missing ping opens the incident after the grace window without any working code on the failure side.
-
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.
One global filter covers every Hangfire job you'll ever add.
Fifteen lines now beat debugging a silent batch job later. First alert wired up in about a minute — and LastPing monitors itself the same way.