The check passed.
consecutiveFailures resets to 0 on every pass, so recovery is immediate and total.
/health tells you the truth at the moment you call it. Healthie.NET runs each check on its own interval, escalates only once failures repeat, and keeps the history — so you find out on the third timeout rather than on the first support ticket.
dotnet add package Healthie.NETCheckout API
200 OK · p95 64ms
Primary Database
SELECT 1 · 3ms
Payments Gateway
Gateway timeout after 5s · 3 in a row
Order Worker
Queue drained · 0 pending
Edge Cache
Hit rate 92%
TLS Certificate
Valid · expires in 54 days
read-only · the full board is further down
Same input · two readings
Both lanes below are reading the identical sequence of check results. The only difference is whether a failure has to repeat before it is allowed to mean anything.
Two lanes read the same sequence of health check results. Read as pass or fail, three separate failures each register as an outage. Read with a consecutive-failure threshold of 2, the single failure and the pair are held as Suspicious, and only the run of four escalates to Unhealthy.
Three failures above, one outage below. The threshold is a constructor argument per checker, so the flaky third-party API and the database do not have to share your patience.
The exact rule →The three-state model
Up-or-down means a single dropped connection looks exactly like a dead database. Healthie.NET adds a state in between and a count of how many failures in a row it has seen, so escalation is a property of the check rather than a rule you rewrite in every alerting tool.
The check passed.
consecutiveFailures resets to 0 on every pass, so recovery is immediate and total.
It failed, but not often enough yet.
A failure at or below the threshold is held here rather than promoted. One timeout is weather, not climate.
It has failed past its threshold.
Now it counts. The threshold is per checker, so a flaky API and a database can hold different patience.
failed → consecutiveFailures += 1
passed → consecutiveFailures = 0
failed and consecutiveFailures > threshold
→ promote to Unhealthy
unhealthy and consecutiveFailures ≤ threshold
→ demote to SuspiciousThe threshold is a constructor argument on each checker, so a third-party API you expect to wobble and a database you do not can hold different patience without a rule living anywhere else.
The default is 0. At zero the comparison is > 0, so the first failure escalates immediately and the three-state model stays out of your way until you ask for it.
A failure to reach the state store is not a failed check — it throws rather than recording a result, because writing "unhealthy" when the storage is down would report a healthy component as broken.
Live · not a screenshot
Read-only, so nothing on it can be changed from here. It is watching the real status endpoints of services you probably depend on — when one of them has a bad afternoon, you will see it here before you see it anywhere else on this page.
Built from the published NuGet packages at version 4.1.0, not from a local checkout — if the packages on nuget.org were broken, this board would be broken too. State is in-memory, so it resets whenever the demo redeploys.
Nothing above? Open the board directly — some networks and privacy extensions block embedded frames.
Writing one
Return a state and a message. The interval, the failure threshold, the group and the tags are constructor arguments and properties, so the schedule lives next to the check rather than in configuration somewhere else.
public sealed class DatabaseChecker(IStateProvider state)
: PulseChecker(
state,
PulseInterval.Every30Seconds,
unhealthyThreshold: 2)
{
public override string DisplayName => "Primary Database";
public override string? DefaultGroup => "Data";
public override IReadOnlyList<string> DefaultTags =>
["tier-1", "sql"];
public override async Task<PulseCheckerResult> CheckAsync(
CancellationToken ct = default)
{
await using var connection =
new SqlConnection(_connectionString);
await connection.OpenAsync(ct);
return new PulseCheckerResult(
PulseCheckerHealth.Healthy,
"SELECT 1 responded");
}
}// Finds every pulse checker and starts
// monitoring on the intervals they declare.
// No scheduler to configure, no storage
// to stand up.
builder.Services
.AddHealthie(typeof(Program).Assembly);
// The dashboard, served at /healthie/dashboard
builder.Services.AddHealthieUI();
app.MapHealthieUI();Assembly scanning registers every PulseChecker it finds as a singleton. There is no list to keep in step, and nothing else to wire — the in-memory state provider and the timer scheduler are registered by default, so this runs standalone before you have chosen anything.
Or write none of it
The checks you already have, on a schedule, with history.
Every registered IHealthCheck — yours, or the community ones for SQL Server, Redis, RabbitMQ, Azure or AWS — becomes a pulse checker with intervals and thresholds. Nothing is rewritten and nothing is replaced.
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieForHealthChecks();What it does
One package gets you running. A provider, a scheduler, the dashboard or the REST API is a separate install, and each exists to keep its driver off machines that do not need it. Each tile below names the package the claim lives in.
The one most people miss
Already have IHealthCheck implementations — yours, or the community ones for SQL Server, Redis, RabbitMQ, Azure or AWS? One call gives every one of them intervals, thresholds and history, with nothing rewritten.
One wrinkle worth knowing: HealthStatus.Degraded maps to Suspicious, but the two do not mean quite the same thing — Degraded is impaired but working, Suspicious is a failure not yet confirmed by repetition. At the default threshold of 0 a degraded check therefore reports Unhealthy on its first failure. Give it a threshold of at least 1.
builder.Services.AddHealthChecks()
.AddSqlServer(connectionString, name: "orders-db")
.AddRedis(redisConnectionString, name: "cache");
builder.Services.AddHealthie();
// Schedules every health check above, with a
// threshold, history, and the dashboard.
builder.Services.AddHealthieForHealthChecks(
PulseInterval.Every30Seconds,
unhealthyThreshold: 2);in Healthie.NET · call it after your AddHealthChecks() registrations
It tells someone
Slack, Teams, PagerDuty, a webhook, or your own sink. Recovery notices and flap suppression included — and a webhook that is down cannot delay a check or make a healthy component look unhealthy.
Healthie.NET.Alerting
It reports on itself
Those two need no package at all. Uptime over an arbitrary window — which a hundred-entry history cannot answer — is one more.
AddHealthieUptime();Healthie.NET.Uptime
It scales out
Leader election runs the checks on one replica at a time, so three instances do not check everything three times.
AddHealthieLeaderElection();Healthie.NET.LeaderElection
Nothing to stand up
A PeriodicTimer scheduler and an in-memory store are registered by default. Swap in Quartz or Postgres when you outgrow them; your checker code does not change.
in Healthie.NET
Built for agents
So an agent can read health and act on it only if you let it. Kubernetes liveness and readiness probes come with the REST API, and the dashboard has no third-party dependencies and no web fonts, so it runs air-gapped.
Healthie.NET.Mcp · .Api · .Dashboard
The ecosystem
Healthie.NET is not a bundle — it depends on one package and nothing else, so no database driver, scheduler or UI framework lands on your machine unless you name it. Everything below is a decision you get to defer.
State is written every tick. In-memory loses it on restart; these do not. Separate because each carries a database driver.
Healthie.NET.PostgresPostgreSQL, including Databricks Lakebase
Healthie.NET.SqlServerSQL Server and Azure SQL
Healthie.NET.SqliteDurable, with no server to stand up
Healthie.NET.CosmosDbAzure CosmosDB
Healthie.NET.RedisThe fastest, for state written every tick
Healthie.NET.RelationalThe engine behind the three SQL providers. Point it at any ADO.NET database
The built-in timer schedules in-process and forgets on restart, which is right for most apps. These put the schedule somewhere that outlives it.
Healthie.NET.QuartzQuartz.NET, if you already run it
Healthie.NET.HangfireSchedules survive a restart; each occurrence runs on one replica
Healthie.NET.CoravelCoravel, if you already run it
Healthie.NET.TemporalSchedules live in the cluster
Somewhere to look, or something to call.
Healthie.NET.DashboardThe Blazor dashboard. Zero third-party dependencies, no web fonts, runs air-gapped
Healthie.NET.ApiREST endpoints for managing checkers, plus liveness and readiness probes
Healthie.NET.McpA Model Context Protocol server, so an agent can read and act on health
Alerting, ready-made checkers, uptime, AI — and the one that stops three replicas doing the same work three times.
Healthie.NET.AlertingHealth changes become alerts — Slack, Teams, PagerDuty, a webhook, or your own sink
Healthie.NET.CheckersHTTP endpoints, TCP ports, TLS expiry, DNS and disk space, with no checker code to write
Healthie.NET.UptimeUptime and SLA over any window, by recording transitions rather than every check
Healthie.NET.LeaderElectionRuns the checks on one replica at a time, so a scaled-out app checks each component once
Healthie.NET.AIExplains a checker's recent failures through any IChatClient
For library authors shipping a provider of their own.
Healthie.NET.AbstractionsThe contracts and PulseChecker, with exactly one dependency
Healthie.NET.DependencyInjectionWhat Healthie.NET resolves to. Reference it directly if you would rather be explicit
One version, all of it
Every package in this list is published at the same version, together.
There is no matrix to reason about and no provider trailing two minors behind the core it plugs into. Upgrading is one number.
current · 4.1.0
Trust your uptime
Add the package, write one class, and there is a dashboard. Everything after that — a provider, a scheduler, alerting, the REST API — is a decision you get to defer.
dotnet add package Healthie.NET