Blog

Hangfire vs Quartz.NET vs TickerQ vs Coravel: .NET job schedulers in 2026

An honest comparison of the four in-process .NET job schedulers — persistence, dashboards, clustering, cron support, maintenance status — and when an external scheduler beats all of them.

SteadyCron dotnethangfirequartztickerqcoravelscheduling

Every .NET team scheduling background work ends up comparing the same libraries. Here’s the honest version of that comparison — including the option the library docs won’t mention.

The 30-second answer

  • Quartz.NET — the most mature and the most capable scheduler as a scheduler: rich cron triggers, calendars, misfire policies, DB-backed clustering. No dashboard, more ceremony.
  • Hangfire — the most popular for background jobs in general: fire-and-forget, delayed, and recurring jobs with a great dashboard. Recurring cron is a feature, not the core.
  • TickerQ — the newcomer: source-generator based (no reflection), EF Core persistence, a live dashboard, and no polling loop. Modern, but young — check maturity against your risk tolerance.
  • Coravel — the simplest: fluent in-process scheduling with zero infrastructure. No persistence — a restart loses state, a single instance is assumed.
  • FluentScheduler — historically popular, now effectively in maintenance mode; fine in old codebases, hard to recommend for new ones.

Feature comparison

Quartz.NETHangfireTickerQCoravel
Cron schedulesYes (6-field, rich)Yes (recurring jobs)YesFluent API (Daily(), Cron())
PersistenceADO.NET storesSQL/Redis storageEF CoreNone (in-memory)
Survives restartsYes (with store)YesYesNo
Multi-node clusteringYes (DB locks)Yes (storage-based)YesNo
DashboardNo (third-party)YesYesNo
RetriesVia listeners/policiesAutomatic, configurableConfigurableManual
How jobs runPolling + in-memoryStorage pollingIn-memory, no pollingIn-memory timers
WeightHeavyMediumLight-mediumVery light

Deeper dive on the two incumbents: Quartz.NET vs Hangfire.

What all four share — and can’t fix

They all run inside your application process. That has real consequences:

  1. Your app must be running. A scheduler inside an app that crashed, is deploying, or scaled to zero schedules nothing. On IIS, app-pool recycling silently pauses everything until the next request warms the site.
  2. The schedule state lives in your database (or nowhere, for Coravel) — your job table, your polling load, your migrations.
  3. Nobody outside the process is watching. If the host dies at 01:59 and the backup was due at 02:00, every in-process library fails identically: silently. The scheduler can’t alert you about its own death.

Problems 1 and 2 are architecture trade-offs you might happily accept. Problem 3 is the one worth fixing regardless of which library you pick: pair the schedule with an external heartbeat monitor — the job pings when it completes; a missing ping becomes an email/Slack/Discord/Telegram alert. The .NET SDK wraps this in one attribute.

When to skip the library entirely

If a job boils down to “call this endpoint on a schedule” — cleanups, report generation, cache warming, webhook dispatch — an in-process scheduler is infrastructure you don’t need. Expose the work as an HTTP endpoint and let an external scheduler call it with retries, timeouts, and an execution log:

jobs:
  - id: nightly-report
    name: Nightly report
    kind: http
    method: POST
    url: https://app.example.com/internal/reports/nightly
    schedule: "0 2 * * *"
    timezone: Europe/Berlin
    retries: 3
    headers:
      Authorization: Bearer ${CRON_SECRET}

No job tables, no polling load, no dashboard to host — and the schedule survives your deploys. The full trade-off against Hangfire specifically: Hangfire alternative.

Recommendations

  • Complex scheduling logic (calendars, misfire handling, chained triggers): Quartz.NET.
  • General background-job system with enqueue/retry/dashboard: Hangfire.
  • Greenfield, EF Core shop, want a dashboard without Hangfire’s weight: evaluate TickerQ.
  • Small app, single instance, jobs that can miss a beat: Coravel.
  • “Call this URL on a schedule” jobs, or anything that must not fail silently: externalize the trigger, keep the handler — whatever else you run.