Coravel vs Hangfire: when the simpler one is the right answer
Coravel vs Hangfire for .NET scheduling — zero-infrastructure fluent scheduling against a persistent, dashboarded job system, and how to tell which you need.
This comparison usually gets framed as “lightweight vs full-featured”, which makes it sound like Coravel is the compromise choice. It isn’t. It’s a different bet about where job state should live — and for a lot of applications it’s the correct bet.
The one difference everything follows from
Coravel keeps schedule state in memory. Hangfire keeps it in a database.
Every other difference is downstream of that:
| Coravel | Hangfire | |
|---|---|---|
| Infrastructure required | None | SQL Server, PostgreSQL, Redis, … |
| Setup | AddScheduler(), one lambda | Storage, migrations, dashboard auth |
| Survives a restart | No | Yes |
| Two instances | Both fire every job | Coordinated via storage |
| Job history | None | Full, in the dashboard |
| Retries | You write them | Automatic, configurable |
| Fire-and-forget from a request | Yes (in-memory queue) | Yes (persistent) |
| Also does | Caching, mailing, event broadcasting | Batches, continuations (Pro) |
| Licence | MIT | LGPL v3 core, paid Pro |
Coravel’s setup is genuinely this small:
services.AddScheduler();
app.Services.UseScheduler(scheduler =>
{
scheduler.Schedule<NightlyCleanup>().DailyAt(2, 0);
scheduler.Schedule<SyncInventory>().EveryFifteenMinutes().PreventOverlapping("sync");
});
No tables, no migrations, no dashboard route to secure. For a small app that is not a lesser version of Hangfire — it’s the absence of a problem.
What in-memory actually costs
Be concrete about it, because the trade only works if you’re honest:
- A restart loses the schedule’s state. Deploy at 01:59 and the 02:00 job doesn’t run late — it doesn’t run. There’s no store to notice it was missed.
- Horizontal scaling breaks the semantics. Three instances means three
copies of every scheduled job firing simultaneously.
PreventOverlappingis per-process; it does not coordinate across instances. Your job handlers have to be idempotent, or you stay at one instance. - There’s no history. When someone asks whether last Tuesday’s job ran, the answer is whatever your logs happened to capture.
- Retries are yours to write. Coravel will not retry a failed job for you.
If none of those four sentences describe a problem you have, Coravel is the better tool, and installing Hangfire instead means running a database dependency to solve problems you don’t have.
When to pick Hangfire
- You run more than one instance of the app. This is usually the whole decision — it’s the one Coravel limitation you cannot engineer around from the outside.
- Jobs are financially or legally consequential and “it silently didn’t run” is unacceptable.
- You need job history for debugging or for an auditor.
- You enqueue substantial fire-and-forget work from request handlers and need it to survive a crash. Coravel has an in-memory queue; a crash empties it.
- You want retries with backoff without writing them.
When to pick Coravel
- Single instance. A monolith on one VM, a container that scales vertically, an internal tool.
- Jobs are idempotent and recoverable — a sync that catches up next run, a cache warm, a digest email that can be a few minutes late.
- You’d otherwise be adding a database purely for the scheduler.
- You want its other pieces too. Coravel’s caching, mailing, and event broadcasting often replace three more dependencies, which is a real argument that has nothing to do with scheduling.
One thing to check before you commit: Coravel’s release cadence has slowed considerably — the latest NuGet package is 6.0.2 from January 2025. For a small, stable library that does one thing, “finished” is a legitimate state and not the same as abandoned. But if you need a fix landed on someone else’s timetable, weigh that. Hangfire and Quartz.NET both ship far more frequently.
The middle path most people miss
The framing above assumes the choice is which library runs inside my app. For recurring jobs specifically, there’s a third option: don’t keep the schedule in the process at all.
If the job is “call this endpoint every night at 02:00”, the schedule can live outside, and your handler stays exactly where it is:
jobs:
- id: nightly-cleanup
name: Nightly cleanup
kind: http
method: POST
url: https://app.example.com/internal/jobs/cleanup
schedule: "0 2 * * *"
timezone: Europe/Berlin
retries: 3
headers:
Authorization: Bearer ${CRON_SECRET}
That gets you Coravel’s zero-infrastructure story and the durability Hangfire uses a database for: the schedule survives restarts because it was never in the process, multiple instances don’t multiply the job because only one HTTP call is made, and you get retries and an execution log without a job table. Full trade-off: Hangfire alternative.
It genuinely doesn’t help with fire-and-forget work enqueued from a request handler. Keep Coravel’s queue or Hangfire’s for that.
Either way: notice when it stops
Coravel’s in-memory model makes silent failure more likely — there’s no store holding evidence that a job was due — but Hangfire fails quietly too when the process is down. Neither library can alert you about its own death.
Whichever you pick, put one outside witness on the jobs that matter: the job pings when it finishes, and a missing ping becomes the alert. The .NET SDK is one attribute, and heartbeat monitoring is free down to once a minute. For a single-instance Coravel app it’s the cheapest possible insurance against the failure mode you actually signed up for.
Still choosing? The four-way comparison: Best .NET job scheduler: Hangfire, Quartz.NET, TickerQ, Coravel.
Frequently asked questions
Should I use Coravel or Hangfire?
Coravel if your app runs as a single instance and a missed job is recoverable — you get scheduling with no database, no dashboard to secure, and almost no ceremony. Hangfire the moment job state has to survive a restart or be coordinated across replicas. The deciding question isn't features, it's whether losing an in-flight job is an inconvenience or an incident.
Does Coravel persist scheduled jobs?
No. Coravel schedules in memory, so a restart, crash, or deploy loses queued and in-flight work, and the schedule simply resumes from the new process. That's the trade it makes deliberately in exchange for needing no storage at all — it isn't an oversight to configure around.
Can Coravel run on multiple instances?
Not safely without help. Coravel assumes a single scheduler; run two replicas and every scheduled job fires on each of them. Hangfire, Quartz.NET, and TickerQ coordinate through shared storage. If you're on more than one instance and want to stay on Coravel, the schedule has to be triggered from somewhere singular — an external scheduler calling one endpoint, for example.
Is Coravel good enough for production?
Yes, for the shape of app it targets: single instance, jobs that are idempotent or cheap to miss, no requirement to inspect job history. Plenty of production systems fit that description. It stops being good enough the moment you scale out or need a record of what ran.