A health endpoint should not only prove that the web process can return 200 OK. It should answer the question your platform or operator is actually asking: can this instance safely receive traffic, or should it stay out of rotation?
That does not mean every health check should call every dependency on every probe. A database query, cache call, queue check, or downstream HTTP request can become load if it runs too often or does too much work. Test dependencies deliberately, with cheap checks, timeouts, and a clear reason for why that dependency affects readiness.
Separate liveness from readiness. A liveness probe should usually answer whether the process is alive and should not trigger restarts because a database is temporarily slow. A readiness probe can be stricter because it controls whether traffic should be sent to the instance.
In ASP.NET Core, that usually means tagging checks and mapping separate endpoints:
builder.Services
.AddHealthChecks()
.AddDbContextCheck<AppDbContext>(
name: "database",
tags: new[] { "ready" });
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
The liveness endpoint proves the process can respond. The readiness endpoint includes dependency checks that decide whether the instance should receive traffic.
For dependency checks, prefer simple signals over realistic work. Opening a connection may be enough. If you run a query, keep it tiny and predictable. Avoid checks that create data, depend on external rate limits, call expensive AI models, or hide real production failures behind broad exception handling.
Health checks are part of the reliability contract. Make the result meaningful, cheap, and aligned with what the orchestrator will do when it fails.