Almost every .NET application eventually needs work that runs outside of a web request: sending emails, importing files, generating reports, syncing with other systems. There are four common ways to do it — Hangfire, Quartz.NET, the younger TickerQ, and the built-in BackgroundService. They solve overlapping but different problems. This comparison helps you pick the right one for your project.
As of September 2026, based on each project's documentation. Details change between releases — check the current docs for anything that decides your choice.
| Hangfire | Quartz.NET | TickerQ | BackgroundService | |
|---|---|---|---|---|
| Main model | Job queue + scheduler | Scheduler (jobs + triggers) | Scheduler (“tickers”) | Long-running loop in your process |
| Persistence | Yes — SQL Server built in; PostgreSQL, Redis and others via official Pro or community packages | Optional — in-memory by default, ADO.NET job store for SQL Server, PostgreSQL, MySQL, SQLite and others | Yes — via Entity Framework Core; in-memory also possible | No |
| Fire-and-forget / queued jobs | Yes | Possible via one-off triggers, not its focus | Yes (one-off “time tickers”) | Build it yourself (e.g. with Channels) |
| Recurring (cron) jobs | Yes | Yes, plus simple and calendar-based triggers | Yes | Build it yourself (PeriodicTimer) |
| Automatic retries | Yes, 10 attempts with back-off by default | No built-in retry policy; a job can request a re-fire | Yes, configurable | No |
| Built-in dashboard | Yes | No (community projects exist) | Yes | No |
| Survives a restart | Yes | Only with a persistent job store | With persistence enabled | No — in-flight work is lost |
| License | LGPL 3.0 core, commercial Pro add-ons | Apache 2.0 | Open source | Part of .NET |
| Maturity | Around since 2014, very large install base | One of the oldest .NET schedulers | Young project, growing quickly | — |
Hangfire's core idea is that calling BackgroundJob.Enqueue(() => ...) should be as easy as calling the method directly — but the call is serialized into persistent storage and executed by a worker, possibly on another server, with automatic retries if it fails. Delayed jobs, recurring jobs via cron expressions and continuations are built on the same foundation.
Strengths: very little code to get started, work survives restarts, failed jobs retry automatically, and the built-in dashboard shows queues and recent jobs. It is by far the most widely used option, so almost every problem you will hit has been hit — and answered — before.
Watch out for: the dashboard runs inside your application and only shows what the storage still holds — succeeded jobs expire after about a day by default (why, and what to do). Changing a job's method signature while old jobs are still queued breaks them. Redis storage and batches are part of the commercial Pro packages.
Quartz.NET is a port of the Java Quartz scheduler and separates jobs (what to do) from triggers (when to do it). A single job can have several triggers; triggers can be cron-based, simple intervals, or combined with calendars that exclude, for example, public holidays. Misfire instructions define exactly what happens when a run was missed because the scheduler was down.
Strengths: the most expressive scheduling model of the four, clustering with a shared database job store, permissive Apache 2.0 license, and years of production use.
Watch out for: it is not a work queue — enqueueing thousands of ad-hoc jobs from your code is not what it is designed for. There is no built-in retry policy (a failing job can ask to be re-fired, but back-off and attempt limits are up to you) and no official dashboard. The default in-memory job store loses everything on restart.
TickerQ takes a different technical approach: jobs are discovered with source generators instead of reflection, persistence runs through Entity Framework Core, and a dashboard is available as a package. It distinguishes cron tickers (recurring) from time tickers (run once at a given time).
Strengths: a clean, modern API, good fit for applications that already use EF Core, no reflection at runtime.
Watch out for: it is a young project. Fewer people have run it through years of production edge cases, and there are fewer answered questions when something goes wrong. That is not a reason to avoid it, but it is a reason to test the failure cases you care about — restarts, deploys, concurrent servers — before you rely on it.
ASP.NET Core's BackgroundService (an IHostedService) runs a long-lived loop inside your process. Combined with PeriodicTimer or System.Threading.Channels, it covers simple needs without any dependency.
Strengths: zero dependencies, full control, nothing to configure.
Watch out for: there is no persistence, no retry, no history and no visibility. Anything in flight when the process stops is gone. With several replicas, every replica runs the loop, so you need your own locking to avoid doing the work twice. Once you catch yourself building retries and a status table, it is time for a framework.
BackgroundService, and Quartz.NET with the in-memory store.Built-in dashboards show the current state inside the running application; they do not push alerts, and they disappear with the process they live in. QueueHawk covers that gap for Hangfire: an agent streams every job state change out of your application, keeps a searchable history across deploys, and alerts via Slack, Teams, email or webhook on error rates and missing heartbeats. It does not support Quartz.NET, TickerQ or hosted services today. If you schedule with cron expressions in any of them, the cron expression explainer shows exactly when a job will run.
Neither is better in general. Hangfire is the stronger choice when you mainly enqueue work from your application and want retries, persistence and a dashboard out of the box. Quartz.NET is stronger when scheduling itself is the hard part: complex triggers, calendars that exclude holidays, and precise misfire handling.
TickerQ is used in production by a growing number of teams, but it is much younger than Hangfire and Quartz.NET, so its ecosystem of storage options, integrations and answered questions is smaller. Evaluate it against your own requirements and check its release history and issue tracker before committing.
When the work is cheap to lose and easy to redo: polling a queue that itself is durable, refreshing a cache, or a periodic cleanup where a missed run does not matter. As soon as work must not be lost on restart, needs retries, or needs a history, use a job framework.
Not today. QueueHawk's agent hooks into Hangfire's own state-change pipeline, so it currently supports Hangfire only. Support for other schedulers depends on demand; if you need it, tell us at team@queuehawk.com.
History that survives deploys, error-rate and heartbeat alerts. Free for one application.