Health Checks in ASP.NET Core
Health checks let your application report whether it can serve traffic. Orchestrators like Kubernetes, load balancers, and monitoring tools use these endpoints to decide whether to route requests to an instance, restart it, or raise an alert.
Basic Setup
ASP.NET Core includes health check infrastructure out of the box:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/healthz");
app.Run();
A GET request to /healthz returns a 200 OK with the body Healthy. That is enough for a basic liveness check, but real applications need to verify their dependencies.
Checking Dependencies
Create a custom health check by implementing IHealthCheck:
public class DatabaseHealthCheck : IHealthCheck
{
private readonly IDbContextFactory<AppDbContext> _factory;
public DatabaseHealthCheck(IDbContextFactory<AppDbContext> factory)
{
_factory = factory;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
using var db = await _factory.CreateDbContextAsync(cancellationToken);
await db.Database.CanConnectAsync(cancellationToken);
return HealthCheckResult.Healthy("Database connection is available.");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(
"Database connection failed.", ex);
}
}
}
Register it with tags so you can group checks into different endpoints:
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>(
"database",
failureStatus: HealthStatus.Unhealthy,
tags: new[] { "ready" })
.AddCheck<RedisHealthCheck>(
"redis",
failureStatus: HealthStatus.Degraded,
tags: new[] { "ready" });
Liveness vs Readiness
A common pattern in container orchestration is to separate liveness and readiness probes:
- Liveness: Is the process running and not deadlocked? (Should we restart it?)
- Readiness: Can it handle traffic? (Should we send it requests?)
// Liveness — always healthy if the process is running
app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
Predicate = _ => false // Run no checks — just return healthy
});
// Readiness — check all dependencies tagged "ready"
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
In a Kubernetes deployment, configure these as separate probes:
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
Custom Response Format
By default, the health endpoint returns a plain text status. For monitoring dashboards, a JSON response with per-check details is more useful:
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
duration = report.TotalDuration.TotalMilliseconds,
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description,
duration = e.Value.Duration.TotalMilliseconds,
exception = e.Value.Exception?.Message
})
};
await context.Response.WriteAsJsonAsync(result);
}
});
Using NuGet Packages
The AspNetCore.HealthChecks.* community packages provide pre-built checks for common dependencies:
builder.Services.AddHealthChecks()
.AddSqlServer(
builder.Configuration.GetConnectionString("Default")!,
name: "sql-server",
tags: new[] { "ready" })
.AddRedis(
builder.Configuration.GetConnectionString("Redis")!,
name: "redis",
tags: new[] { "ready" })
.AddUrlGroup(
new Uri("https://api.example.com/status"),
name: "external-api",
tags: new[] { "ready" });
These packages cover SQL Server, PostgreSQL, Redis, RabbitMQ, Azure services, and dozens more.
Health Check UI
The AspNetCore.HealthChecks.UI package provides a dashboard that polls your health endpoints and displays their status over time. This is particularly useful during development and for internal monitoring:
builder.Services.AddHealthChecksUI()
.AddInMemoryStorage();
app.MapHealthChecksUI();
Key Takeaways
Health checks are essential infrastructure for any production application. Separate liveness from readiness probes, tag your checks for flexible endpoint composition, and return structured JSON responses for monitoring tools. The community NuGet packages save considerable time for common dependency checks.