Some work should not block a web request: sending emails, generating reports, syncing data, cleaning old logs. ASP.NET Core MVC apps solve this with Hangfire, an in-process job scheduler that persists jobs in your database and executes them reliably — even after a restart. This article sets up Hangfire in a .NET 10 MVC application and shows fire-and-forget, delayed, and recurring jobs with the built-in dashboard.
Setting Up Hangfire
Install the Hangfire and Hangfire.SqlServer packages, add Hangfire services, and map the dashboard:
builder.Services.AddHangfire(config =>
config.UseSqlServerStorage(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddHangfireServer();
var app = builder.Build();
app.UseHangfireDashboard("/hangfire");
Hangfire creates its own tables in the configured database, so jobs and their state survive application restarts. The dashboard at /hangfire shows queues, retries, and failed jobs in real time.
Fire-and-Forget Jobs
Enqueue one-off work from a controller. The job runs asynchronously on the Hangfire worker pool, and the HTTP request returns immediately:
public class InvoiceController : Controller
{
public IActionResult Send(int id)
{
// runs in the background, request returns instantly
BackgroundJob.Enqueue(() => _invoiceService.SendAsync(id));
return RedirectToAction("Index");
}
}
Only public methods on registered services should be called. Hangfire serializes the method call, so the target class must be resolved from DI — never call anonymous lambdas or private methods.
Delayed and Recurring Jobs
Delayed jobs run once after a timeout; recurring jobs run on a cron schedule:
// delayed: send a reminder in 1 hour
BackgroundJob.Schedule(() => _mailer.SendReminderAsync(id), TimeSpan.FromHours(1));
// recurring: run the log cleanup every day at 03:00
RecurringJob.AddOrUpdate<SerilogCleanupJob>(
job => job.ExecuteAsync(CancellationToken.None),
"0 3 * * *");
Recurring jobs are idempotent by design — the scheduler ensures they do not overlap, and the dashboard lets you trigger, pause, and delete schedules without redeploying.
Protecting the Dashboard
The dashboard exposes internals, so restrict it to admins in production:
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireAdminFilter() }
});
public class HangfireAdminFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var http = context.GetHttpContext();
return http.User?.IsInRole("Admin") == true;
}
}
A Practical Sample: SerilogCleanupJob
Combine background jobs with logging. A recurring job that deletes logs older than three days keeps storage bounded without any manual maintenance — this exact job ships in the MVC EDevKit Basic starter:
public class SerilogCleanupJob
{
public async Task ExecuteAsync(CancellationToken ct)
{
var cutoff = DateTime.UtcNow.AddDays(-3);
var old = await _db.LogEntries.Where(l => l.CreatedAt < cutoff).ToListAsync(ct);
if (old.Count == 0) return;
_db.LogEntries.RemoveRange(old);
await _db.SaveChangesAsync(ct);
}
}
Key Takeaways
- Hangfire persists jobs in your database, surviving restarts
- Fire-and-forget, delayed, and recurring job types cover the common scheduling needs
- The
/hangfiredashboard gives you queues, retries, and failures at a glance - Protect the dashboard with an admin-only authorization filter
- Pair recurring jobs with logging and cleanup to keep production storage bounded
FAQ
Does Hangfire work with SQL Server? Yes, SQL Server is the most widely used Hangfire storage, and PostgreSQL and Redis storage are also available.
Will jobs survive an application restart? Yes. Jobs and their state are persisted in the database, and Hangfire picks them up again when the server starts.
What happens if a job throws? Hangfire retries automatically with exponential backoff and marks the job as failed after the retry limit, where you can inspect and replay it from the dashboard.
Can recurring jobs overlap? Hangfire prevents overlapping executions of the same recurring job, so a slow run will not start a second instance.