Mastering ASP.NET Core Health Checks (with UI) — A Deep Dive
Based on the official Microsoft documentation on health checks in ASP.NET Core Microsoft Learn
Plus extensions like HealthChecks.UI from the open-source community GitHub
Introduction
In modern web APIs, it’s essential to have self-monitoring baked in. Health checks let your application report its own status, so:
- Load balancers or container orchestrators (Kubernetes, Azure App Service, etc.) can detect when an instance is unhealthy and stop routing traffic to it. Microsoft Learn
- Monitoring systems (Prometheus, Grafana, Application Insights, etc.) can query health endpoints to alert you before users notice. Microsoft Learn
- Developers and ops teams get visibility into dependencies (database, cache, external APIs).
This blog covers:
- Basic health check setup
- Built-in and community health checks
- Custom health checks
- Advanced endpoint configurations (filtering, readiness/liveness)
- HealthChecks UI for visualization
- Best practices & tips
1. Basic Health Probe (Liveness)
By default, ASP.NET Core includes health check middleware (via Microsoft.AspNetCore.Diagnostics.HealthChecks). The process is:
- Register health check services with
AddHealthChecks() - Map a health endpoint using
MapHealthChecks(...)
Here’s the minimal setup in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Register health check services
builder.Services.AddHealthChecks();
var app = builder.Build();
// Map a health endpoint
app.MapHealthChecks("/healthz");
app.Run();
With no specific health checks registered, the health endpoint returns Healthy (HTTP 200) as long as the app is running.
The default response is plaintext showing Healthy, Degraded or Unhealthy (from the HealthStatus enum).
Healthy→ HTTP 200Degraded→ HTTP 200 (by default)Unhealthy→ HTTP 503 (Service Unavailable)
You can customize HTTP status codes and output using HealthCheckOptions when mapping.
2. Built-In & Community Health Checks
Microsoft / Core health checks
ASP.NET Core’s built-in library primarily gives the infrastructure for health checks (middleware, interfaces). For particular subsystems (SQL, Redis, etc.), you often rely on external packages.
But there is a “system” health check package:
- AspNetCore.HealthChecks.System — for system-level checks (e.g., disk space) NuGet
Community / Diagnostic HealthChecks by Xabaril
The AspNetCore.Diagnostics.HealthChecks library (open source) provides a suite of ready checks for many common dependencies. GitHub
Examples include:
- SQL Server (
AddSqlServer(...)) - EF Core (via Microsoft’s diagnostics or via the package)
- Redis (
AddRedis(...)) - URL/URI endpoints (
AddUrlGroup(...)) - Disk or system checks (
AddDiskStorageHealthCheck(...)) - TCP / socket checks, MongoDB, RabbitMQ, Azure services, etc.
These reduce boilerplate and speed setup. Microsoft Learn
3. Custom Health Checks
If your application has specialized dependencies (e.g. email service, third-party API, custom business logic), you can implement your own health check via IHealthCheck:
public class EmailServiceHealthCheck : IHealthCheck
{
private readonly IEmailService _emailService;
public EmailServiceHealthCheck(IEmailService emailService)
{
_emailService = emailService;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
bool ok = await _emailService.PingAsync(); // example logic
if (ok)
return HealthCheckResult.Healthy("Email service is responding.");
else
return HealthCheckResult.Unhealthy("Email service failed to respond.");
}
}
Then register it:
builder.Services.AddHealthChecks()
.AddCheck<EmailServiceHealthCheck>("EmailService");
You can also pass a failureStatus or tags:
.AddCheck<EmailServiceHealthCheck>(
"EmailService",
failureStatus: HealthStatus.Degraded,
tags: new[] { "essential" });
Tags are handy for grouping and filtering.
4. Advanced Endpoint Configuration: Readiness, Liveness, Filtering
In containerized and microservices environments (e.g. Kubernetes), it’s common to separate:
- Liveness: Is the application alive (i.e. not crashed)?
- Readiness: Are all dependencies healthy so it can accept traffic?
You can configure multiple endpoints with filtering:
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // maybe no checks—just liveness
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
When registering checks:
builder.Services.AddHealthChecks()
.AddSqlServer(connStr, tags: new[] { "ready" })
.AddRedis(redisConn, tags: new[] { "ready" })
.AddCheck<EmailServiceHealthCheck>("EmailService", tags: new[] { "ready" });
This way /health/live might always succeed (if app is running), whereas /health/ready returns failure if dependencies are failing.
You can also configure other things in HealthCheckOptions:
Predicateto select subset of checksAllowCachingResponsesResponseWriter(to output JSON, detailed info)StatusCodesoverride for mappingDegraded/Unhealthyto specific HTTP codes
You can also short-circuit or branch the pipeline (e.g. ShortCircuit()) so health checks don’t run other middleware.
5. HealthChecks UI / Dashboard
A core limitation of the default health check endpoint is that it’s raw (text or minimal JSON). To visualize health of multiple services or endpoints, you can use the HealthChecks UI project (by Xabaril) — a lightweight dashboard for health checks.
Setup
Install the following NuGet packages:
AspNetCore.HealthChecks.UI— the UI frontend & dashboardAspNetCore.HealthChecks.UI.Client— client abstractions- Storage (in-memory or persistent) — e.g.
AddInMemoryStorage()
Then configure:
// In ConfigureServices
builder.Services
.AddHealthChecks()
.AddSqlServer(connStr, tags: new[] { "ready" })
// … other checks …
;
builder.Services.AddHealthChecksUI(options =>
{
options.AddHealthCheckEndpoint("My API", "/health/ready");
})
.AddInMemoryStorage();
Then map the UI middleware:
app.MapHealthChecksUI(ui =>
{
ui.UIPath = "/health-ui"; // Dashboard path
ui.ApiPath = "/health-ui-api"; // API for internal use
});
The UI dashboard will poll the endpoints you configured and show status over time (green, yellow, red).
You can host the UI in the same app or in a separate “monitoring” app (watchdog) that polls multiple services.
Notes & Tips
- The UI internally stores health reports (via a background host) for the frontend to query.
- Don’t confuse the UI’s internal API (e.g.
/healthchecks-api) with your health endpoints. - For each health endpoint that you want to visualize, you should configure a
ResponseWriterusingUIResponseWriter.WriteHealthCheckUIResponse(...)so the UI can parse details. - You can configure webhooks or alerts from UI when a service becomes unhealthy.
6. Sample Project (Program.cs)
Here’s a consolidated sample Program.cs that demonstrates many of the above features:
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
// Register health checks
builder.Services.AddHealthChecks()
.AddSqlServer(builder.Configuration.GetConnectionString("Default"), tags: new[] { "ready" })
.AddRedis(builder.Configuration["Redis:Conn"], tags: new[] { "ready" })
.AddCheck<EmailServiceHealthCheck>("EmailService", tags: new[] { "ready" });
// Add HealthChecks UI
builder.Services.AddHealthChecksUI(config =>
{
config.AddHealthCheckEndpoint("Main API", "/health/ready");
})
.AddInMemoryStorage();
var app = builder.Build();
// Liveness endpoint — checks nothing, just app is alive
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false
});
// Readiness endpoint — checks tagged “ready” checks
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// Health UI dashboard
app.MapHealthChecksUI(ui =>
{
ui.UIPath = "/health-ui";
ui.ApiPath = "/health-ui-api";
});
app.MapControllers();
app.Run();
You’ll need to write your EmailServiceHealthCheck as shown previously, and have connection strings configured in appsettings.json.
7. Best Practices & Tips
- Use tags consistently: Tag checks (e.g.
"ready","critical") so you can filter for readiness or partial checks. - Use the UI only for internal or protected endpoints — don’t expose it publicly unless secured.
- Use
UIResponseWriterfor endpoints you intend to show on the dashboard, so the UI can parse details. - Customize status codes if you want
Degradedto return 200 or 429, etc. - Separate liveness vs readiness in container/orchestrator environments.
- Short-circuit health middleware if you want health endpoints to skip expensive middleware (logging, auth, etc.).
- Consider a dedicated watchdog service with HealthChecks UI that monitors multiple microservices.
- Secure the health endpoints using authentication/authorization or network rules (firewalls, internal subnets).
- Monitor over time: use the UI’s stored results or integrate with metrics systems like Prometheus or Azure Monitor.
- Be cautious with expensive checks (like full DB queries) — health checks should be lightweight and fast.
Conclusion
Health checks in ASP.NET Core are a powerful yet lightweight way to make your application self-aware.
With a bit of setup, you can:
- Expose liveness / readiness endpoints
- Monitor dependencies (SQL, Redis, external APIs)
- Provide custom logic via
IHealthCheck - Filter and configure endpoints
- Visualize health status via dashboards using HealthChecks UI
All of this is backed by the Microsoft health check infrastructure Microsoft Learn and enhanced by community solutions like AspNetCore.Diagnostics.HealthChecks and HealthChecks.UI GitHub | Microsoft Learn