Skip to content

Monitor Hangfire recurring jobs

Hangfire retries failed jobs and shows them on its dashboard, which is helpful only if someone opens it. When the Hangfire server isn’t running, a job keeps failing through its retries, or the recurring job was removed in a deploy, nobody is told. A heartbeat check catches all of these: the job reports to Pingstack after it succeeds, and Pingstack alerts you when that report doesn’t arrive on time.

1. Create a heartbeat check that matches the schedule

Section titled “1. Create a heartbeat check that matches the schedule”

Find the recurring job’s registration:

RecurringJob.AddOrUpdate<NightlyReportJob>(
"nightly-report",
job => job.RunAsync(),
"0 2 * * *",
new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });

Create a heartbeat check and set its schedule to Cron with the same expression. Hangfire uses UTC unless you set TimeZone; choose the same timezone in Pingstack. See schedules and timezones.

Set a grace period that covers the job’s runtime and its retries. Hangfire retries a failed job automatically, with growing delays, so a job that succeeds on a later attempt can report much later. Attach a notification group.

Copy the location’s ping URL from the check in Pingstack and provide it as configuration, such as the environment variable Pingstack__NightlyReportUrl in production or dotnet user-secrets in development. Don’t put it in appsettings.json.

public class NightlyReportJob(
IReportService reports,
IHttpClientFactory httpFactory,
IConfiguration config,
ILogger<NightlyReportJob> log)
{
public async Task RunAsync()
{
await reports.SendNightlyAsync();
var url = config["Pingstack:NightlyReportUrl"];
if (string.IsNullOrEmpty(url)) return;
try
{
var http = httpFactory.CreateClient();
http.Timeout = TimeSpan.FromSeconds(10);
(await http.GetAsync(url)).EnsureSuccessStatusCode();
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException)
{
log.LogWarning("Pingstack heartbeat failed: {Error}", e.GetType().Name);
}
}
}

The report runs only after SendNightlyAsync() completes, so a run that throws sends nothing and Pingstack alerts you when the deadline passes. A failure to reach Pingstack is logged, not thrown, so it can’t make Hangfire retry a successful job. Register IHttpClientFactory with builder.Services.AddHttpClient().

Open the Hangfire dashboard, go to Recurring Jobs, select the job and choose Trigger now. When it succeeds, open the check in Pingstack and confirm the location received a heartbeat and shows a next deadline.

Append a count as a numeric value and set warning and critical thresholds on the check:

var sent = await reports.SendNightlyAsync();
await http.GetAsync($"{url}?value={sent}");
  • Retries. A job that succeeds on its fourth attempt reports hours late. Either lengthen the grace period or reduce retries for this job with [AutomaticRetry(Attempts = 2)].
  • No server. Recurring jobs are queued but not run if no Hangfire server is processing that queue.
  • Timezone mismatch. The check and RecurringJobOptions.TimeZone must agree.

If alerts still don’t match what you expect, see troubleshooting monitoring.