Modern applications often require background processing tasks such as sending emails, generating reports, or cleaning up data. Hangfire is a powerful library for background job processing in .NET applications. Its key features include reliable job execution, dashboard monitoring, and cron-based scheduling.
This article provides a deep dive into Hangfire cron jobs, including setup, advanced scheduling, best practices, and real-world considerations.
1. What is Hangfire?
Hangfire is an open-source library that allows you to run background jobs in .NET applications without writing a separate Windows Service or console app.
Key features
Persistent background jobs with SQL Server, Redis, or other storage.
Supports Fire-and-forget jobs, Delayed jobs, Recurring jobs, and Continuations.
Provides a Dashboard UI for monitoring jobs.
Scales with multiple servers in a distributed environment.
2. Hangfire Job Types
| Job Type | Description |
|---|
| Fire-and-Forget | Executes once immediately after enqueueing. |
| Delayed | Executes once after a specific delay. |
| Recurring | Executes repeatedly based on a cron expression. |
| Continuations | Executes after a parent job completes. |
This article focuses on Recurring Jobs using Cron.
3. Setting Up Hangfire in ASP.NET Core
3.1. Install NuGet Packages
dotnet add package Hangfire
dotnet add package Hangfire.AspNetCore
dotnet add package Hangfire.SqlServer
Hangfire supports multiple storage options, but SQL Server is most common in enterprise apps.
3.2. Configure Hangfire in Program.cs
using Hangfire;
using Hangfire.SqlServer;
var builder = WebApplication.CreateBuilder(args);
// Add Hangfire services
builder.Services.AddHangfire(config =>
{
config.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
});
});
// Add Hangfire Server
builder.Services.AddHangfireServer();
var app = builder.Build();
// Configure Dashboard
app.UseHangfireDashboard("/hangfire");
// Example Fire-and-Forget Job
BackgroundJob.Enqueue(() => Console.WriteLine("Hello Hangfire!"));
app.Run();
3.3. Add Connection String
appsettings.json:
{"ConnectionStrings": {
"HangfireConnection": "Server=localhost;Database=HangfireDB;Trusted_Connection=True;"}}
4. Understanding Cron Expressions
Hangfire uses Cron expressions to schedule recurring jobs. They follow this pattern:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday=0)
│ │ │ │ │
* * * * *
Hangfire provides a Cron class with pre-built schedules:
| Method | Cron Expression | Frequency |
|---|
| Cron.Minutely() | * * * * * | Every minute |
| Cron.Hourly() | 0 * * * * | Every hour |
| Cron.Daily() | 0 0 * * * | Once daily at midnight |
| Cron.Weekly() | 0 0 * * 0 | Every Sunday midnight |
| Cron.Monthly() | 0 0 1 * * | 1st of every month |
| Cron.Yearly() | 0 0 1 1 * | Every January 1st |
| Cron.Weekly(DayOfWeek.Monday) | 0 0 * * 1 | Every Monday midnight |
You can also provide custom cron expressions like "30 14 * * 1-5" (Weekdays at 2:30 PM).
5. Creating Recurring Jobs
5.1. Simple Recurring Job
using Hangfire;
RecurringJob.AddOrUpdate(
"daily-report", // Job ID
() => Console.WriteLine("Daily report generated."), // Method to call
Cron.Daily); // Cron schedule
5.2. Job With Parameters
public class ReportService
{
public void GenerateReport(string reportType)
{
Console.WriteLine($"Generating {reportType} report at {DateTime.Now}");
}
}
// Schedule
RecurringJob.AddOrUpdate<ReportService>(
"sales-report",
x => x.GenerateReport("Sales"),
Cron.Daily);
Hangfire automatically serializes parameters.
Supports complex objects, but they must be serializable.
6. Using Cron Expressions for Advanced Scheduling
Every 15 minutes: "*/15 * * * *" → Cron.MinuteInterval(15)
Weekdays at 9 AM: "0 9 * * 1-5" → Custom expression
Last day of the month at 11:59 PM: "59 23 L * *" → Hangfire supports advanced cron extensions using NCrontab.Advanced
Example
RecurringJob.AddOrUpdate(
"quarterly-report",
() => Console.WriteLine("Quarterly report generated"),
"0 0 1 1,4,7,10 *"); // 1st of Jan, Apr, Jul, Oct at midnight
7. Using Hangfire Dashboard
Hangfire provides a built-in dashboard for monitoring jobs:
URL: /hangfire
Shows job statistics: succeeded, failed, processing, scheduled
Allows retrying failed jobs, deleting jobs, and triggering jobs manually
Supports authorization filters
Add authorization
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new MyAuthorizationFilter() }
});
using Hangfire.Dashboard;
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
// Only allow local requests
var httpContext = context.GetHttpContext();
return httpContext.Request.IsLocal();
}
}
8. Distributed Job Execution
Hangfire can scale across multiple servers:
Multiple ASP.NET Core instances can share the same SQL Server storage.
Jobs are locked per server to prevent duplicate execution.
Useful for cloud deployments (Azure App Service, Kubernetes, Docker).
Recommended settings for SQL Server storage
.UseSqlServerStorage(connectionString, new SqlServerStorageOptions
{
QueuePollInterval = TimeSpan.FromSeconds(15),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
});
9. Handling Failures and Retries
Hangfire retries failed jobs automatically.
Default retry count: 10 times with exponential backoff.
Customize retry logic using AutomaticRetry attribute:
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new int[] { 10, 60, 300 })]
public void SendEmail(string email)
{
// send email logic
}
10. Logging and Monitoring
public class ReportService
{
private readonly ILogger<ReportService> _logger;
public ReportService(ILogger<ReportService> logger)
{
_logger = logger;
}
public void GenerateReport(string reportType)
{
_logger.LogInformation("Generating {ReportType} report at {Time}", reportType, DateTime.Now);
}
}
11. Best Practices for Hangfire Cron Jobs
Use AddOrUpdate to prevent duplicate recurring jobs.
Assign unique job IDs for all recurring jobs.
Keep jobs idempotent – safe to run multiple times without side effects.
Avoid long-running jobs – break into smaller jobs or use batches.
Secure the Dashboard with authentication/authorization.
Monitor retries and failures to prevent silent failures.
Prefer async methods for I/O-bound tasks.
Use persistent storage (SQL Server or Redis) for production.
12. Example Real-World Scenario
Scenario: Send daily sales reports at 6 AM every day, weekly summary on Monday 8 AM, and monthly invoices on the 1st of each month.
RecurringJob.AddOrUpdate<ReportService>(
"daily-sales",
x => x.GenerateReport("Daily Sales"),
Cron.Daily(6));
RecurringJob.AddOrUpdate<ReportService>(
"weekly-summary",
x => x.GenerateReport("Weekly Summary"),
Cron.Weekly(DayOfWeek.Monday, 8));
RecurringJob.AddOrUpdate<InvoiceService>(
"monthly-invoices",
x => x.GenerateInvoices(),
"0 0 1 * *"); // 1st day of each month at midnight
All jobs are monitored via Dashboard.
Failures are automatically retried.
Jobs can be triggered manually if needed.
Conclusion
Hangfire provides a robust, scalable, and developer-friendly way to manage background jobs in ASP.NET Core applications. With cron expressions, you can schedule tasks flexibly, monitor them with the built-in dashboard, and ensure reliable execution.
Key takeaways:
Recurring jobs use cron expressions to schedule tasks.
AddOrUpdate prevents duplicate recurring jobs.
Jobs should be idempotent, monitored, and logged.
Hangfire scales to multiple servers and works well in distributed systems.
By following best practices, Hangfire can handle almost any enterprise background job scenario efficiently.