How */15 * * * * works
*/15 in the minute field means every minute from 0 to 59 that is a multiple of 15: :00, :15, :30, :45. The schedule follows the clock, not the moment your app started: a job registered at 10:03 first runs at 10:15.
A step that does not divide 60 restarts every hour. */7 * * * * runs at :49 and :56, and then at :00, only four minutes later.
Variations
| Schedule | Hangfire / crontab | Azure Functions | Quartz.NET |
|---|---|---|---|
| Every 15 minutes, starting at minute 2 | 2-59/15 * * * * | 0 2-59/15 * * * * | 0 2-59/15 * * * ? |
| Every 15 minutes, 09:00 to 17:59, Monday to Friday | */15 9-17 * * MON-FRI | 0 */15 9-17 * * MON-FRI | 0 */15 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(),
"*/15 * * * *",
new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });
// Cronos on its own
var schedule = CronExpression.Parse("*/15 * * * *");
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 */15 * * * *")] TimerInfo timer)
{
}
// NCrontab on its own
var schedule = CrontabSchedule.Parse("*/15 * * * *");
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 */15 * * * ?", 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
Overlap depends on the library. Hangfire starts a new background job on schedule regardless of whether the last one finished, unless [DisableConcurrentExecution] is on the job method; Quartz.NET needs [DisallowConcurrentExecution] on the job class for the same protection. An Azure Functions timer trigger is different: it will not fire again while a previous invocation is still running, even when the app has scaled out.
FAQ
What is the cron expression for every 15 minutes?
*/15 * * * * in Linux crontab, Hangfire (Cronos) and NCrontab; 0 */15 * * * * in an Azure Functions timer trigger, which has a seconds field; 0 */15 * * * ? in Quartz.NET.
Does every 15 minutes count from when my app starts?
No. Cron follows the clock: */15 * * * * runs at fixed times (every 15 minutes), whenever the app was started or the job registered.
Is there a Hangfire shortcut for it?
Yes: Cron.MinuteInterval(15) returns "*/15 * * * *" in Hangfire 1.8.
Other common schedules
- Cron every minute
* * * * * - Cron every 5 minutes
*/5 * * * * - Cron every hour
0 * * * * - Cron expression builder for .NET (any expression, in all three libraries)