DEV Community

Cover image for Mastering Hangfire in .NET 9: A Complete Guide to Background Jobs
Madusanka Bandara
Madusanka Bandara

Posted on

Mastering Hangfire in .NET 9: A Complete Guide to Background Jobs

1. Introduction

In modern web applications, not all tasks should run during the main user request. Some processes—like sending emails, generating reports, cleaning up logs, or syncing data—can take seconds or even minutes to complete. Running these tasks inline would slow down the application and create a poor user experience. This is where background jobs come into play.

A background job is any task that runs outside of the main request/response cycle. Instead of making users wait for time-consuming operations, the application delegates these tasks to run in the background. This improves performance, keeps the application responsive, and allows developers to handle recurring or long-running work reliably.

For example:

  • Sending a confirmation email after user registration.
  • Generating invoices or reports at scheduled intervals.
  • Processing uploaded files (e.g., resizing images, parsing CSVs).
  • Performing database cleanups or backups.

With the release of .NET 9, Microsoft continues to provide strong support for background processing. Developers can use built-in approaches like IHostedService or BackgroundService for simple scenarios. However, for more advanced requirements—such as retries, job persistence, and monitoring—frameworks like Hangfire offer a much more robust solution.

In this article, we’ll explore how to leverage Hangfire in .NET 9 to implement reliable, scalable, and maintainable background jobs.


2. What is Hangfire?

Hangfire is an open-source framework for managing background jobs in .NET applications. It allows developers to run fire-and-forget, delayed, recurring, or continuation jobs without writing complex infrastructure code. Hangfire handles job execution, persistence, retries, and monitoring out of the box, making it one of the most popular choices for background processing in the .NET ecosystem.

At its core, Hangfire uses a persistent storage (SQL Server, PostgreSQL, Redis, etc.) to queue and track jobs. This ensures that even if your application restarts or crashes, pending jobs will still be processed reliably.

Here’s why Hangfire often makes a better choice:

  • Persistence: Jobs survive application restarts and crashes.
  • Retries & Error Handling: Built-in retry logic prevents job loss.
  • Monitoring: The Dashboard provides visibility into job execution.
  • Scalability: Can run multiple servers to process jobs concurrently.
  • Flexibility: Supports different storage providers (SQL, Redis, etc.).

3. Setting Up Hangfire in .NET 9

Now that we understand what Hangfire is and why it’s powerful, let’s walk through the setup in a .NET 9 Web API or MVC project. Currently I'm choosing to web api project to implement Hangfire background job.

3.1 Installing Required NuGet Packages

Add the below NuGet packages to the project.

Hangfire.AspNetCore
Hangfire.SqlServer
Enter fullscreen mode Exit fullscreen mode

Hangfire.AspNetCore → Integrates Hangfire with ASP.NET Core apps
Hangfire.SqlServer → Enables SQL Server as persistent storage for jobs

If you’re using a different storage provider (e.g., PostgreSQL, Redis), install the respective package.

3.2 Configuring Hangfire in Program.cs

In .NET 9, most configurations go inside Program.cs.

using Hangfire;

var builder = WebApplication.CreateBuilder(args);

// 1. Add Hangfire services and configure SQL Server storage
builder.Services.AddHangfire(config =>
    config.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));

// 2. Add Hangfire background processing server
builder.Services.AddHangfireServer();

var app = builder.Build();

// 3. Enable Hangfire Dashboard
app.UseHangfireDashboard("/hangfire");

// Example: register a sample job
app.MapGet("/", (IBackgroundJobClient backgroundJobs) =>
{
    backgroundJobs.Enqueue(() => Console.WriteLine("Hello from Hangfire!"));
    return "Background job has been queued.";
});

app.Run();

Enter fullscreen mode Exit fullscreen mode

3.3 Connecting with SQL Server for Storage

In your appsettings.json, define the Hangfire connection string:

{
  "ConnectionStrings": {
    "HangfireConnection": "Server=localhost;Database=HangfireDB;User Id=sa;Password=YourPassword;TrustServerCertificate=True;"
  }
}

Enter fullscreen mode Exit fullscreen mode

4. Types of Background Jobs in Hangfire

One of Hangfire’s biggest strengths is the variety of job types it supports. Depending on your scenario, you can run jobs immediately, after a delay, on a schedule, or chained together. Let’s go through each type with examples.

4.1 Fire-and-Forget Jobs

These are executed immediately and only once. They are useful for tasks that don’t need to block the main user request.


using Hangfire;

public class EmailService
{
    public void SendWelcomeEmail(string email)
    {
        Console.WriteLine($"Welcome email sent to {email}");
    }
}

// Usage
BackgroundJob.Enqueue<EmailService>(service => service.SendWelcomeEmail("user@example.com"));

Enter fullscreen mode Exit fullscreen mode

4.2 Delayed Jobs

These jobs are scheduled to run after a specified delay.

BackgroundJob.Schedule(() => Console.WriteLine("This job runs after 2 minutes"), 
                       TimeSpan.FromMinutes(2));
Enter fullscreen mode Exit fullscreen mode

4.3 Recurring Jobs (with Cron)

Recurring jobs run on a schedule, similar to CRON jobs in Linux.

RecurringJob.AddOrUpdate("daily-report", 
    () => Console.WriteLine("Generating daily report..."), 
    Cron.Daily);
Enter fullscreen mode Exit fullscreen mode

Other Cron expressions provided by Hangfire:

Cron.Minutely → every minute
Cron.Hourly → every hour
Cron.Daily → once a day
Cron.Weekly → once a week
Cron.Monthly → once a month

4.4 Continuation Jobs

These jobs run only after a parent job has finished.

var parentJobId = BackgroundJob.Enqueue(() => Console.WriteLine("Step 1: First job executed"));

BackgroundJob.ContinueJobWith(parentJobId, 
    () => Console.WriteLine("Step 2: Continuation job executed"));
Enter fullscreen mode Exit fullscreen mode

5. Exploring the Hangfire Dashboard

One of Hangfire’s most powerful features is its interactive dashboard. It provides real-time visibility into background jobs, queues, and processing servers. With just a browser, you can monitor the health of y

5.1 Navigating the UI

Once enabled in your app:

app.UseHangfireDashboard("/hangfire");
Enter fullscreen mode Exit fullscreen mode

You can open http://localhost:5000/hangfire in the browser.

The dashboard is divided into several sections:

  • Jobs → View jobs by state (Succeeded, Failed, Scheduled, Processing, Deleted).
  • Recurring Jobs → See all scheduled recurring tasks with their CRON expressions.
  • *Queues *→ Monitor job queues and check how many workers are processing them.
  • Servers → Displays active Hangfire servers running your jobs.
  • Retries → Track jobs that have failed and are waiting for a retry.

5.2 Monitoring Running & Completed Jobs

From the Jobs tab, you can:

  • Check which jobs are currently running.
  • See detailed logs for completed jobs.
  • Inspect job parameters and execution history.

5.3 Retrying Failed Jobs

If a job fails (due to a network error, database timeout, etc.), Hangfire automatically retries it a few times based on the retry policy.

  • You can manually retry a failed job from the dashboard.
  • Failed jobs are highlighted with detailed exception logs.
  • This makes Hangfire extremely reliable for critical tasks like sending invoices or payments.

5.4 Securing the Dashboard with Authentication

By default, the dashboard is open to anyone who knows the URL. This is fine for local development but must be secured in production.

You can add authentication using DashboardOptions:

using Hangfire.Dashboard;

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new MyDashboardAuthorizationFilter() }
});

public class MyDashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        // Example: allow only authenticated users
        var httpContext = context.GetHttpContext();
        return httpContext.User.Identity?.IsAuthenticated ?? false;
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Advanced Usage Patterns

Once you’re comfortable with basic Hangfire jobs, you can unlock its advanced features to build more scalable and maintainable systems. Let’s explore some common patterns.

6.1 Using Dependency Injection in Hangfire Jobs

Hangfire integrates with ASP.NET Core’s built-in Dependency Injection (DI) container. This allows you to inject services directly into your job methods instead of writing static helpers.

public interface IEmailService
{
    void SendWelcomeEmail(string email);
}

public class EmailService : IEmailService
{
    public void SendWelcomeEmail(string email)
    {
        Console.WriteLine($"Email sent to {email}");
    }
}

// Register service in Program.cs
builder.Services.AddScoped<IEmailService, EmailService>();

// Enqueue job with DI
BackgroundJob.Enqueue<IEmailService>(service => service.SendWelcomeEmail("user@example.com"));
Enter fullscreen mode Exit fullscreen mode

6.2 Creating a Custom Job Scheduler with Cron

For advanced scheduling, you can define custom CRON expressions.

Example: Run a cleanup job every Monday at midnight.

RecurringJob.AddOrUpdate("weekly-cleanup",
    () => Console.WriteLine("Performing weekly cleanup..."),
    "0 0 * * 1"); // Cron format: minute hour day month weekday
Enter fullscreen mode Exit fullscreen mode

6.3 Handling Concurrency & Queue Management

By default, all jobs run in the default queue. For more control, you can assign jobs to custom queues and configure workers accordingly.

// Enqueue job into a custom "emails" queue
BackgroundJob.Enqueue(() => Console.WriteLine("Email Job"), new EnqueuedState("emails"));
Enter fullscreen mode Exit fullscreen mode

In Program.cs, configure Hangfire Server to listen to multiple queues:

builder.Services.AddHangfireServer(options =>
{
    options.Queues = new[] { "default", "emails", "reports" };
});
Enter fullscreen mode Exit fullscreen mode

6.4 Scaling Hangfire with Multiple Servers

Hangfire is designed for horizontal scaling. You can run multiple instances of your application (on different servers or containers), and they will all share the same job storage (e.g., SQL Server or Redis).

  • Each server pulls jobs from the shared storage.
  • Jobs are distributed across workers automatically.
  • Failed jobs are retried by any available server.

This makes Hangfire suitable for high-traffic, production workloads.

builder.Services.AddHangfireServer(options =>
{
    options.WorkerCount = Environment.ProcessorCount * 5; // Scale with CPU cores
});
Enter fullscreen mode Exit fullscreen mode

7. Best Practices for Production

Running Hangfire in production requires careful planning to ensure reliability, security, and performance. Below are some best practices you should follow:

7.1. Logging & Monitoring Background Jobs

  • Integrate structured logging (e.g., with Serilog or NLog) to track job execution.
  • Use Hangfire’s built-in job history tracking for insights.
  • Consider external monitoring tools like Application Insights, ELK stack, or Prometheus/Grafana for deeper visibility.
public class EmailService
{
    private readonly ILogger<EmailService> _logger;

    public EmailService(ILogger<EmailService> logger)
    {
        _logger = logger;
    }

    public void SendWelcomeEmail(string userEmail)
    {
        _logger.LogInformation("Sending welcome email to {User}", userEmail);
        // email sending logic
    }
}
Enter fullscreen mode Exit fullscreen mode

7.2 Retry Policies & Error Handling

  • Hangfire automatically retries failed jobs (default: 10 attempts).
  • Configure retry policies based on job criticality.
  • Use [AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)] to fine-tune.
public class ReportGenerator
{
    [AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
    public void GenerateDailyReport()
    {
        // Report generation logic
    }
}
Enter fullscreen mode Exit fullscreen mode

7.3. Securing Hangfire Dashboard in Production

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new HangfireCustomAuthorizationFilter() }
});
Enter fullscreen mode Exit fullscreen mode

Custom filter:

public class HangfireCustomAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        var httpContext = context.GetHttpContext();
        return httpContext.User.Identity?.IsAuthenticated == true &&
               httpContext.User.IsInRole("Admin");
    }
}
Enter fullscreen mode Exit fullscreen mode

7.4. Database Performance Considerations

  • Hangfire stores jobs in SQL Server; optimize DB for high insert/update throughput.
  • Use separate DB schema or instance if you have high job volume.
  • Clean up old job history with Hangfire’s built-in housekeeping jobs.
app.UseHangfireServer(new BackgroundJobServerOptions
{
    Queues = new[] { "default" },
    WorkerCount = Environment.ProcessorCount * 5 // Tune based on load
});

Enter fullscreen mode Exit fullscreen mode

8. Conclusion

Background job processing is a critical part of building modern, scalable applications, and Hangfire makes it both simple and powerful in the .NET 9 ecosystem. With its persistence, retry mechanisms, flexible job types, and intuitive dashboard, Hangfire provides a production-ready framework for handling tasks that don’t belong in the main request pipeline.

While BackgroundService may be sufficient for lightweight or one-off background tasks, Hangfire shines when you need reliability, visibility, and scalability. By following best practices—such as securing the dashboard, tuning retry policies, and monitoring system performance—you can ensure your background jobs run smoothly in production environments.

As .NET 9 continues to improve performance and developer experience, pairing it with Hangfire allows you to build robust, fault-tolerant, and scalable background processing systems that enhance both your application’s performance and your users’ experience.

Top comments (0)