How 0 * * * * works
0 * * * * runs at minute 0 of every hour: 00:00, 01:00, 02:00 and so on. The first field is the minute; to run at a different minute past each hour, change it (15 * * * * runs at 00:15, 01:15, ...).
Variations
| Schedule | Hangfire / crontab | Azure Functions | Quartz.NET |
|---|---|---|---|
Every hour at minute 15 (Cron.Hourly(15)) | 15 * * * * | 0 15 * * * * | 0 15 * * * ? |
| Every hour, 09:00 to 17:00, Monday to Friday | 0 9-17 * * MON-FRI | 0 0 9-17 * * MON-FRI | 0 0 9-17 ? * MON-FRI |
C# code
Hangfire
// Hangfire: recurring jobs use Cronos. Times are UTC unless you set a time zone.
RecurringJob.AddOrUpdate<ReportJob>(
"daily-report",
job => job.RunAsync(),
"0 * * * *",
new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });
// Cronos on its own
var schedule = CronExpression.Parse("0 * * * *");
DateTime? next = schedule.GetNextOccurrence(DateTime.UtcNow);
Azure Functions
// Azure Functions (isolated worker): six fields, seconds first. Times are UTC
// unless WEBSITE_TIME_ZONE is set on the app.
[Function("DailyReport")]
public void Run([TimerTrigger("0 0 * * * *")] TimerInfo timer)
{
}
// NCrontab on its own
var schedule = CrontabSchedule.Parse("0 * * * *");
DateTime next = schedule.GetNextOccurrence(DateTime.UtcNow);
Quartz.NET
// Quartz.NET with Microsoft.Extensions.DependencyInjection
services.AddQuartz(q =>
{
var jobKey = new JobKey("daily-report");
q.AddJob<ReportJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(t => t
.ForJob(jobKey)
.WithIdentity("daily-report-trigger")
.WithCronSchedule("0 0 * * * ?", x => x.InTimeZone(TimeZoneInfo.Utc)));
});
Common mistakes
Day numbers that mean different days in Quartz.NET, ranges that wrap in Azure Functions and the time zone each library assumes catch people with every schedule, not only this one. They are listed once, with fixes, on the cron expression builder.
When a run takes longer than the interval
Hangfire creates a new background job at every occurrence, even while the previous one is still running; add [DisableConcurrentExecution] to the job method if runs must not overlap. Quartz.NET also starts a new run on time unless the job class has [DisallowConcurrentExecution]. An Azure Functions timer trigger does not fire again while an invocation is still running, even when the app has scaled out.
FAQ
What is the cron expression for every hour?
0 * * * * in Linux crontab, Hangfire (Cronos) and NCrontab; 0 0 * * * * in an Azure Functions timer trigger, which has a seconds field; 0 0 * * * ? in Quartz.NET.
Does every hour count from when my app starts?
No. Cron follows the clock: 0 * * * * runs at fixed times (at minute 0), whenever the app was started or the job registered.
Is there a Hangfire shortcut for it?
Yes: Cron.Hourly() returns "0 * * * *" in Hangfire 1.8.
Other common schedules
- Cron every minute
* * * * * - Cron every 5 minutes
*/5 * * * * - Cron every 15 minutes
*/15 * * * * - Cron expression builder for .NET (any expression, in all three libraries)