• Home
  • /
  • Articles
  • /
  • Software Development
  • /
  • Hangfire in ASP.NET Core: Setup, API Guide & Examples (2026)
HEADLINES

Hangfire in ASP.NET Core: Setup, API Guide and Examples

Hangfire lets you run background work in a .NET app without building a separate worker service or message broker. You write a normal C# method call, Hangfire stores it in a database, and a background server runs it, with retries, scheduling and a built-in dashboard. This guide installs the latest release (1.8.24 at the time of writing) in an ASP.NET Core app and walks through the API with working code.


  • Techm Studios
  • BY TECHM STUDIOS
  • |
  • Sept 29, 2026
  • |
  • UPDATED: Sept 29, 2026
  • |
  • 0 COMMENTS
Hangfire in ASP.NET Core: Setup, API Guide & Examples (2026)
© Medium / Images

What Hangfire is and when to use it

Hangfire is an open-source background job framework for .NET, licensed under LGPL v3. Jobs are persisted in storage (SQL Server by default), so they survive app restarts and can be processed by any server connected to the same database. Use it for sending emails, generating reports, calling slow third-party APIs, nightly cleanups and anything else that should not block an HTTP request.

It is not a full message bus. If you need cross-service messaging, pub/sub or strict ordering guarantees, look at MassTransit or a broker such as RabbitMQ. For "run this code later, reliably, and show me what happened," Hangfire is hard to beat.

Install and configure Hangfire in ASP.NET Core

1. Create the project and add packages

dotnet new web -n HangfireDemo
cd HangfireDemo
dotnet add package Hangfire --version 1.8.24

The Hangfire meta package pulls in Hangfire.Core, Hangfire.AspNetCore and Hangfire.SqlServer. Check NuGet for a newer patch before you install.

2. Add a connection string

Create the (empty) database first. Hangfire creates its own tables on startup.

{
  "ConnectionStrings": {
    "HangfireConnection": "Server=(localdb)\\MSSQLLocalDB;Database=HangfireDemo;Integrated Security=true;TrustServerCertificate=True"
  }
}

3. Register Hangfire in Program.cs

using Hangfire;
using Hangfire.SqlServer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHangfire(config => config
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(
        builder.Configuration.GetConnectionString("HangfireConnection"),
        new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout       = TimeSpan.FromMinutes(5),
            SlidingInvisibilityTimeout   = TimeSpan.FromMinutes(5),
            QueuePollInterval            = TimeSpan.Zero,
            UseRecommendedIsolationLevel = true,
            DisableGlobalLocks           = true
        }));

// Runs the processing server inside this app
builder.Services.AddHangfireServer();

var app = builder.Build();

app.UseHangfireDashboard("/hangfire");   // secure this before production
app.MapGet("/", () => "Hangfire is running");

app.Run();

Run the app and open /hangfire. You should see the dashboard with one active server. The dashboard is restricted to local requests by default, which is why you must add authorization before deploying (see below).

Tip for local development: add the Hangfire.InMemory package and call .UseInMemoryStorage() instead of SQL Server. Jobs vanish on restart, so never use it in production.

Job types and the core API

You create jobs with the static BackgroundJob class or, preferably, the injectable IBackgroundJobClient. Both take an expression tree, so Hangfire records the method, its type and the serialized arguments.

Fire-and-forget jobs

Executed once, as soon as a worker is free.

app.MapPost("/welcome/{userId:int}", (int userId, IBackgroundJobClient jobs) =>
{
    string jobId = jobs.Enqueue<IEmailJobs>(x => x.SendWelcomeAsync(userId, CancellationToken.None));
    return Results.Accepted(value: new { jobId });
});

Delayed jobs

jobs.Schedule<IEmailJobs>(x => x.SendReminderAsync(userId, CancellationToken.None),
                         TimeSpan.FromHours(24));

Continuations

Run a job after its parent finishes successfully.

var parent = jobs.Enqueue<IReportJobs>(x => x.BuildAsync(reportId, CancellationToken.None));
jobs.ContinueJobWith<IEmailJobs>(parent, x => x.SendReportAsync(reportId, CancellationToken.None));

Managing existing jobs

jobs.Delete(jobId);    // remove a job
jobs.Requeue(jobId);   // run it again

Batches and batch continuations are part of the commercial Hangfire Pro package, not the open-source core.

Method arguments: keep them small

Arguments are serialized to JSON and stored. Pass IDs, not entities or large payloads, and load fresh data inside the job. Add a CancellationToken parameter and Hangfire supplies a real token that fires on shutdown or when the job is deleted.

Dependency Injection in Jobs

Hangfire.AspNetCore resolves job classes from your service provider and creates a scope per job, so scoped services such as DbContext work as expected.

public interface IEmailJobs
{
    Task SendWelcomeAsync(int userId, CancellationToken ct);
    Task SendReminderAsync(int userId, CancellationToken ct);
    Task SendReportAsync(int reportId, CancellationToken ct);
}

public class EmailJobs(AppDbContext db, IEmailSender sender, ILogger<EmailJobs> log) : IEmailJobs
{
    public async Task SendWelcomeAsync(int userId, CancellationToken ct)
    {
        var user = await db.Users.FindAsync([userId], ct);
        if (user is null) return;                 // idempotent: nothing to do
        await sender.SendAsync(user.Email, "Welcome!", ct);
        log.LogInformation("Welcome email sent to user {UserId}", userId);
    }
    // ...other methods
}

// Program.cs
builder.Services.AddScoped<IEmailJobs, EmailJobs>();

Recurring jobs

Recurring jobs use CRON expressions and are identified by a unique ID. Calling AddOrUpdate on every startup is safe: it updates the schedule if the ID already exists.

var recurring = app.Services.GetRequiredService<IRecurringJobManager>();

recurring.AddOrUpdate<IReportJobs>(
    "daily-sales-report",
    x => x.BuildDailyAsync(CancellationToken.None),
    Cron.Daily(hour: 2, minute: 30),
    new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Africa/Johannesburg") });

recurring.AddOrUpdate<ICleanupJobs>(
    "purge-expired-tokens",
    x => x.PurgeAsync(CancellationToken.None),
    "*/15 * * * *");                 // every 15 minutes

recurring.TriggerJob("daily-sales-report");      // run now
recurring.RemoveIfExists("purge-expired-tokens");

Hangfire runs recurring jobs in UTC unless you set TimeZone. Use IANA IDs on Linux and containers, and remember that daylight-saving changes can shift local-time schedules.

Retries, queues and filters

Failed jobs are retried automatically, ten times by default with increasing delays. Attributes on the job method or class change that behaviour.

[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 10, 60, 300 },
                OnAttemptsExceeded = AttemptsExceededAction.Delete,
                ExceptOn = new[] { typeof(ValidationException) })]  // ExceptOn: 1.8.15+
[DisableConcurrentExecution(timeoutInSeconds: 60)]
[Queue("critical")]
[JobDisplayName("Build report {0}")]
public Task BuildAsync(int reportId, CancellationToken ct) { /* ... */ }

Queue names must be lowercase letters, digits, underscores or dashes. A server only processes the queues listed in its options, in priority order (see next section).

Writing a custom filter

public class LogFailuresAttribute : JobFilterAttribute, IElectStateFilter
{
    public void OnStateElection(ElectStateContext context)
    {
        if (context.CandidateState is FailedState failed)
            Console.Error.WriteLine($"Job {context.BackgroundJob.Id} failed: {failed.Exception.Message}");
    }
}

GlobalJobFilters.Filters.Add(new LogFailuresAttribute());

Securing the Hangfire dashboard

The dashboard can delete, requeue and trigger jobs, so treat it like an admin page.

public class AdminOnlyDashboardFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        var http = context.GetHttpContext();
        return http.User.Identity?.IsAuthenticated == true && http.User.IsInRole("Admin");
    }
}

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new AdminOnlyDashboardFilter() },
    DashboardTitle = "Background jobs"
});

Alternatively, use endpoint routing so ASP.NET Core policies apply:

app.MapHangfireDashboardWithNoAuthorizationFilters("/hangfire")
   .RequireAuthorization("HangfireAdmins");

That method was added in 1.8.22 and skips Hangfire's own local-only filter, so pair it with a real authorization policy as shown.

Server and storage tuning

builder.Services.AddHangfireServer(options =>
{
    options.ServerName  = $"{Environment.MachineName}:web";
    options.WorkerCount = Math.Min(Environment.ProcessorCount * 5, 20);
    options.Queues      = new[] { "critical", "default" };   // priority order
});
StoragePackageNotes
SQL ServerHangfire.SqlServerOfficial, default choice, auto-creates schema
In-memoryHangfire.InMemoryDev and tests only, non-persistent
PostgreSQLHangfire.PostgreSqlCommunity-maintained
RedisHangfire.Pro.RedisCommercial, very high throughput

To run the server in a separate worker process, call AddHangfire and AddHangfireServer there and use AddHangfire only (no server) in the web app. Both point at the same database.

Production checklist

  • Make jobs idempotent. Retries and rare double execution happen, so a job run twice should be harmless.
  • Pass IDs, not objects, and keep arguments JSON-friendly.
  • Honor CancellationToken so deployments shut down gracefully.
  • Keep hosts alive. On IIS, disable idle timeout or enable Always Running, otherwise recurring jobs stop firing on an idle pool.
  • Set queues deliberately so slow work cannot starve urgent work.
  • Secure the dashboard and consider exposing it only on an internal route.
  • Monitor failures: add a state filter or log sink that alerts on FailedState.
  • Unit test the enqueuing code by mocking IBackgroundJobClient. Enqueue is an extension method, so verify the underlying Create(Job, IState) call.
  • Read the upgrade notes at docs.hangfire.io when moving from 1.7.

Frequently asked questions

Is Hangfire free for commercial use?

The core is LGPL v3 and can be used in commercial products under those terms. Hangfire Pro adds batches, Redis storage and other features under a paid license.

Hangfire or Quartz.NET?

Hangfire favors simplicity: persistent jobs, retries and a dashboard out of the box. Quartz.NET offers richer calendar and trigger models but has no built-in dashboard.

Can I run multiple servers?

Yes. Point them at the same storage and each job is picked up by exactly one worker. Give each server a distinct ServerName and matching queue configuration.

Why is my recurring job not running?

Check that a server is processing the job's queue, that the app is actually running (idle hosts stop), and that the CRON expression and time zone match your expectation.

Conclusion

With one NuGet package, a connection string and a few lines in Program.cs, Hangfire gives ASP.NET Core apps durable background processing. Start with fire-and-forget and recurring jobs, add filters for retries and queues, lock down the dashboard, and keep jobs small and idempotent. For deeper reference, see the official Hangfire documentation.

Also Read: C# Database Connection with Entity Framework Core

SPONSORED ADS

COMMENTS
LEAVE A REPLY
SPONSORED ADS
POPULAR
AI-Generated Fashion Design

AI-generated fashion design is revolutionizing the industry, pushing the boundaries of creativity and innovation in style.


SPONSORED JOBS

TECHM JOBS
POPULAR
Journalist Jobs, South Africa

Browse most trending Journalist jobs in South Africa. Apply today

Photographer Jobs, South Africa

Browse most trending Photographer jobs in South Africa. Apply today

Graphical Designer Jobs, United Kingdom

Browse most trending Fashion jobs in United Kingdom. Apply today


TECHM PRODUCTS


TOP POSTS
STAY IN TOUCH
SPONSORED ADS
Subscribe to Updates

Get the latest creative news from FooBar about art, design and business.

Subscribe