Health checks are automated diagnostics used to determine whether an application and its dependencies are functioning correctly. They regularly assess the health of the app (like checking if it's running, can access a database, or connect to external services) and return a status such as Healthy, Degraded, or Unhealthy.
Health checks are commonly exposed via an HTTP endpoint so monitoring systems or orchestrators (like Kubernetes) can detect issues and respond accordingly (e.g., restart the app or trigger alerts).
Introduction to ASP.NET Core Health Checks
ASP.NET Core Health Checks (provided by the Microsoft.Extensions.Diagnostics.HealthChecks library) offer a built-in way to report the health of an application and its infrastructure dependencies. In essence, a health check is an HTTP endpoint that indicates whether your app and its services are functioning correctly.
This solves the problem of needing standardized "liveness" or "readiness" probes for monitoring systems. For example, container orchestrators (like Kubernetes) or load balancers can call a health check endpoint to determine if an app instance is healthy; if not, the orchestrator might restart the container, or the load balancer can route traffic.
Similarly, health check endpoints can be used by external monitoring tools to trigger alerts when something goes wrong.
Statuses
Health checks are not just binary up/down signals - ASP.NET Core defines three health statuses: Healthy, Degraded, and Unhealthy:
Healthy status means the application is in a normal working state (all checks passed).
Degraded indicates the application is partially running (for example, it's responding but perhaps slower than expected or with some limited functionality), and
Unhealthy means a failure or critical issue was detected.
These statuses let you convey partial failures or performance issues, not just total outages.
Usage
In practice, health checks are typically used alongside an orchestrator or monitoring system. Common use cases include:
Microservice Liveness/Readiness: Container platforms periodically hit a health endpoint to decide if a service should receive traffic or be restarted. For instance, a failing health check might halt a rolling deployment or restart a misbehaving container.
Resource Monitoring: Health checks can monitor system resources like memory, disk space, or CPU usage and report Degraded/Unhealthy if thresholds are crossed.
Dependency Checks: An application can check critical dependencies (databases, message brokers, external APIs, etc.) via health checks to ensure those are reachable and functioning. If a required service (e.g., a database) is down, the app's health check can report Unhealthy, signaling that the app cannot operate normally.
By exposing these standardized health endpoints, you enable automated systems to monitor the app's health in real time and trigger alerts or recovery actions when needed.
Next, we'll see how health checks work in the .NET request pipeline and how to implement them step by step.
How Health Checks Work in the .NET Middleware Pipeline
Under the hood, ASP.NET Core health checks integrate into the middleware pipeline as an endpoint that you can configure. When you register health check services and map a health check endpoint, ASP.NET Core uses the Health Checks Middleware to handle requests to that path. Conceptually, here's how it works:
You define one or more health check routines (by implementing the IHealthCheck interface or using provided checks). Each health check inspects some aspect of your system (for example, database connectivity) and returns a HealthCheckResult indicating the status (Healthy, Degraded, or Unhealthy).
All health checks are registered with the dependency injection container. The registration is done via AddHealthChecks() and then AddCheck for each specific check.
You map a specific route (URL) in your app to the Health Check middleware. For example, mapping /health (or /healthz) as the health endpoint.
When an HTTP GET request hits the configured health endpoint, the Health Checks middleware runs all the registered checks (usually in parallel) and collects their results into a single HealthReport. If you have not registered any specific checks, the health report will by default consider the app healthy if it can respond (useful for a basic liveness check).
The middleware then returns an HTTP response reflecting the aggregate health status. By default, if all checks pass or no checks are registered, the status is Healthy; if any check is Unhealthy, the overall status is Unhealthy (and if any are Degraded with none Unhealthy, the overall can be treated as Healthy in terms of HTTP response code by default). The default response is a plain text body with the overall status (e.g., "Healthy" or "Unhealthy") and an HTTP status code of 200 for Healthy/Degraded or 503 for Unhealthy.
It's important to understand that mapping a health check endpoint inserts a branch in the middleware pipeline specifically for health check requests. This means you can attach conditions or authorization to it if needed (for example, requiring a specific host or authentication for the health URL).
In most cases, health endpoints are exposed internally (or to orchestrators) without authentication, but ASP.NET Core allows configuring rules such as RequireHost("*:5001") or RequireAuthorization() on the map if you need to restrict access.
By default, health check responses are not cacheable (the middleware sets headers to prevent caching) to ensure each health probe is processed fresh. You can customize this or other behaviors (like the returned status codes or the output format) via the HealthCheckOptions when mapping the endpoint. We'll explore an example of customizing the output format to JSON later on.
In summary, once wired up, the health check endpoint acts as a lightweight diagnostic heartbeat for your application within the request pipeline, providing quick insight into the app's status without going through the normal request processing (e.g., no MVC, no heavy logic - just the checks you define).
Benefits and Common Use Cases
To recap and expand on benefits, using Microsoft health checks in your .NET application provides several practical advantages:
- Automated Microservice Monitoring
In a microservices or cloud environment, health check endpoints allow orchestrators like Kubernetes, Azure App Service, or Docker Swarm to automatically monitor and manage your app.
For example, Kubernetes can periodically call the health endpoint to decide if a pod is alive (liveness probe) or ready to receive traffic (readiness probe). Unhealthy responses can trigger automatic restarts or stop deployments, improving resilience.
- Load Balancer Integration
When running multiple instances behind a load balancer, the load balancer can ping the health URL of each instance. If an instance reports Unhealthy, the load balancer can remove it from rotation until it recovers. This ensures users are only routed to healthy instances.
- Dependency and Infrastructure Checks
Health checks provide a unified way to test that critical dependencies (databases, caches, external APIs, etc.) are available. Rather than each service silently failing when a dependency is down, your health endpoint can surface the issue.
This is useful for operations teams to quickly pinpoint what part of the system is unhealthy. For instance, a health check can attempt a simple database query or a cache ping and report Unhealthy if it fails.
By exposing health status in a standardized format, it's easy to plug into alerting systems. A monitoring tool (like Azure Monitor, Application Insights, or a custom script) can periodically GET the /health endpoint. If it receives an Unhealthy status or a failure, it could trigger an alert (email, SMS, etc.) for the on-call team.
Additionally, the Health Checks UI (discussed below) supports webhook notifications - for example, you can configure it to call a Slack or Teams webhook when a health check fails.
- Graceful Degradation Awareness
Because of the Degraded status level, you can signal when the app is up but not fully optimal. For example, if a non-critical subsystem is down or response times are high, a health check could report Degraded. This can be logged or shown in dashboards to address performance issues before they escalate, without causing an immediate outage signal.
- Easy Integration and Extensibility
Microsoft's health check system is extensible. You can write custom checks for virtually anything. Microsoft provides some extension packages (for example, for Entity Framework Core context) and the community has a broad collection of health check packages (for SQL Server, Azure Services, Redis, etc.).
You can also simply implement IHealthCheck yourself for custom needs. In short, you get a uniform approach to monitoring application health across different areas.
Now, let's dive into how to implement health checks in a .NET application step by step.
Implementation: Setting Up Health Checks in ASP.NET Core
In the following sections, we'll walk through how to add health checks to an ASP.NET Core application, how to create custom checks, expose the health endpoint, and finally how to use the HealthChecks UI for a visual dashboard.
This guide applies to .NET 6 and above (using the minimal hosting model in Program.cs), but the concepts are the same for older versions (where you'd register in Startup.ConfigureServices and map the endpoint in Startup.Configure).
1. Installing Required NuGet Packages (if any)
For basic health checks, you don't need any additional NuGet package aside from the ASP.NET Core framework (the health checks APIs are part of Microsoft.Extensions.Diagnostics.HealthChecks). Begin by adding the main NuGet package if it's not already included in your project (most ASP.NET Core templates include it by default):
Microsoft.Extensions.Diagnostics.HealthChecks - core interfaces and types for health checks (usually included in the ASP.NET Core shared framework).
If you plan to use specific health check implementations, you may need extra packages:
- Database Providers: For example, to easily check a SQL Server database, install the
AspNetCore.HealthChecks.SqlServer package (this is a widely-used package that adds .AddSqlServer() extension). There are similar packages for other databases (PostgreSQL, MySQL, etc.) and services. (These come from the AspNetCore.Diagnostics.HealthChecks project, which, while not part of the official Microsoft namespace, is a Microsoft-supported open source extension library.)
For now, let's proceed assuming the necessary packages are available.
2. Register Health Check Services in the DI Container
Registration is straightforward. In your Program.cs (for minimal API) or Startup.ConfigureServices, call AddHealthChecks() on the IServiceCollection (usually via the builder.Services in minimal hosting).
Then use extension methods (e.g. .AddSqlServer(...) ) or the AddCheck method to register specific health checks.
For example:
// Configure health checks
builder.Services.AddHealthChecks()
.AddCheck<AlwaysHealthyCheck>("healthy_check", tags: ["demo"])
.AddCheck<AlwaysUnhealthyCheck>("unhealthy_check", tags: ["demo"]);
.AddCheck<TIHealthCheck>(...) registers a custom health check implementation by its type. We'll create the AlwaysHealthyCheck and AlwaysUnhealthyCheck classes next. We tag them as "demo" in this example.
Tags are an optional way to categorize checks (e.g., "db", "redis", "internal", "external"). Tags become useful if you want to expose multiple health endpoints for different purposes (like a readiness endpoint that runs all checks, versus a liveness endpoint that maybe runs only a simple self-check).
Implementing a Custom Health Check (IHealthCheck)
Custom checks allow you to test anything you want. To create one, make a class that implements the IHealthCheck interface (from the Microsoft.Extensions.Diagnostics.HealthChecks namespace).
This interface has a single method CheckHealthAsync(HealthCheckContext, CancellationToken) that you implement to perform the check. It should return a HealthCheckResult indicating the outcome.
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class AlwaysHealthyCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
// This health check always returns healthy
HealthCheckResult result = HealthCheckResult.Healthy(
description: "This check always returns healthy.",
data: new Dictionary<string, object>
{
{ "description", "Demo health check" },
{ "timestamp", DateTime.UtcNow }
});
return Task.FromResult(result);
}
}
public class AlwaysUnhealthyCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
// This health check always returns unhealthy
HealthCheckResult result = HealthCheckResult.Unhealthy(
description: "This check always returns unhealthy.",
exception: null,
data: new Dictionary<string, object>
{
{ "description", "Demo health check" },
{ "timestamp", DateTime.UtcNow }
});
return Task.FromResult(result);
}
}
3. Exposing a Health Check Endpoint
Once health checks are registered, the next step is to expose an HTTP endpoint for them. In ASP.NET Core minimal hosting, you typically do this after building the WebApplication (and in older Startup-based templates, inside the Configure method). You have two equivalent approaches to wire up the endpoint:
Use Endpoint Routing: e.g. app.MapHealthChecks("/health", options).
Use Middleware (pre-endpoint routing style): e.g. within app.UseEndpoints(...) call endpoints.MapHealthChecks(...) or use app.UseHealthChecks(path, options).
Using the minimal API style, let's map a health endpoint at /health:
// Map health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
// Ensure we run *all* checks (default Behavior)
Predicate = _ => true,
});
At this point, we have a basic health check endpoint set up. If you run the application and navigate to https://<host>/health in a browser or use a tool like curl, you'll get a response indicating the health status.

4. Integrating HealthChecks UI for a Dashboard
1. To add the UI dashboard, you'll need to install the
AspNetCore.HealthChecks.UI package, plus
AspNetCore.HealthChecks.UI.Client (provides the UIResponseWriter for JSON output) and
a storage provider (e.g., AspNetCore.HealthChecks.UI.InMemory.Storage for in-memory storage of results history). For production, you might use a database (there are providers for SQL Server, SQLite, etc., via AddSqlServerStorage(), AddSqliteStorage(), etc.)
2. Let's add UIResponseWriter to previously added app.MapHealthChecks(...):
using HealthChecks.UI.Client;
// Map health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
// Ensure we run *all* checks (default Behavior)
Predicate = _ => true,
// Optionally, customize the output
// Here we use the UIResponseWriter to output detailed JSON
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
The response is changed now:

3. Add the HealthChecks UI services in ConfigureServices (or builder.Services) with needed options. Also add the chosen storage. For example:
// Configure health checks UI
builder.Services.AddHealthChecksUI(options =>
{
// Evaluate status every 5 seconds
options.SetEvaluationTimeInSeconds(5);
// Keep history of last 10 checks
options.MaximumHistoryEntriesPerEndpoint(10);
// Avoid pararell requests
options.SetApiMaxActiveRequests(1);
options.AddHealthCheckEndpoint("Health Checks API", "/health");
})
.AddInMemoryStorage(); // Use in-memory storage for the health check UI
4. Finally, we need to instruct our app to serve the UI. In Program.cs (after mapping other endpoints), do:
// Map health checks UI endpoint
app.MapHealthChecksUI();
This will by default map a couple of endpoints:
The HTML UI is typically served at /healthchecks-ui (you can navigate to this path in a browser to view it).
A backend endpoint for the UI at /healthchecks-api that the UI uses via AJAX to fetch the health check results.
If you need to customize these paths, MapHealthChecksUI(setup => {...}) has options to change the UI path or the API path, but the defaults are usually fine. Once this is done, start your application and visit http://<host>/healthchecks-ui.
Health Checks dashboard
Now the HealthChecks UI will periodically call the configured /health endpoint, gather the results, and display them in a web interface.
Additional HealthChecks UI features
- The UI supports adding multiple endpoints.
This is useful if you have several microservices each with their own health URL; you can configure the UI to track all of them and show a combined dashboard.
- You can also set up webhooks in the UI configuration.
Webhooks are URLs that the UI will call whenever checks fail or recover. For example, you could configure a Slack webhook to post a message if any health check goes Unhealthy. This is an advanced scenario for alerting; it requires specifying the webhook URI and payload in the config. If not needed, you can ignore the Webhooks section.
More details: Failure notification WebHooks - Github
Example of complete setup
Program.cs:
using HC.HealthChecks;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Configure health checks
builder.Services.AddHealthChecks()
.AddCheck<AlwaysHealthyCheck>("healthy_check", tags: ["demo"])
.AddCheck<AlwaysUnhealthyCheck>("unhealthy_check", tags: ["demo"]);
// Configure health checks UI
builder.Services.AddHealthChecksUI(options =>
{
// Evaluate status every 5 seconds
options.SetEvaluationTimeInSeconds(5);
// Keep history of last 10 checks
options.MaximumHistoryEntriesPerEndpoint(10);
// Avoid pararell requests
options.SetApiMaxActiveRequests(1);
options.AddHealthCheckEndpoint("Health Checks API", "/health");
})
.AddInMemoryStorage(); // Use in-memory storage for the health check UI
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// Map health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
// Ensure we run *all* checks (default Behavior)
Predicate = _ => true,
// Optionally, customize the output
// Here we use the built-in UIResponseWriter to output JSON instead of plain text
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// Map health checks UI endpoint
app.MapHealthChecksUI();
app.Run();
Additional Considerations and Best Practices
Health endpoints often expose the internal state of your app (and in the UI, even stack traces for failures). In production, you should protect these endpoints. If the health check is only used by infrastructure (like a cloud orchestrator or a load balancer), consider binding it to localhost or an internal port, or using RequireHost to restrict which host can access it.
If you need it accessible externally for monitoring, consider requiring an API key or authentication token (you can integrate ASP.NET Core Authorization with RequireAuthorization() on the map).
The HealthChecks UI, in particular, should likely be behind some authentication if deployed in a production environment.
Keep health check logic efficient. Health checks are meant to be called frequently (often every few seconds). Expensive operations (like a large database query) could put unnecessary load on your system if done on every probe. Ideally, check just what's necessary (e.g., a simple SELECT 1 query to test DB connectivity, or a lightweight ping to an external service).
The health check system runs checks in parallel by default, so a slow check will not block others, but the overall response waits for all to complete. Aim for health checks that complete quickly (within a second or two at most).
By default, any exception thrown in a health check will result in an Unhealthy status for that check. You can control what status to report on failure when registering a check (for instance, you might designate certain non-critical checks to report Degraded on failure instead of Unhealthy).
The AddCheck overload allows specifying a failureStatus (e.g., always treat failure as HealthStatus.Degraded) if that makes sense for your scenario. This could be used for optional dependencies.
Liveness checks: Is the application alive (not hung) and working at a basic level? This might just check that the main process is responsive and perhaps internal self-tests. Liveness checks usually continue to return Healthy even if dependencies are down, because you might not want a transient DB failure to kill the app container; you just want to restart if the app itself is stuck.
Readiness checks: Is the application fully ready to serve requests? This often includes checking external dependencies (database, message brokers, etc.). If a readiness check fails, it signals that the app should not receive traffic (even if it's technically "up"), because it can't fulfill its duties properly.
The health check system can log the results of checks. If a health check goes Unhealthy, you might want to log it (ASP.NET Core’s health check middleware doesn't automatically log failures, but you can easily do so within your check logic or by writing an event source).
Additionally, the HealthReport object (available in a custom ResponseWriter or in the UI) contains detailed information including any exception messages. Use these to diagnose issues when a health check fails.
- Microsoft Extensions vs Third-Party
We focused on Microsoft-supported tooling. The core health check APIs are built-in, and the HealthChecks.UI (while an external package by the community) is a commonly used, Microsoft-friendly tool to visualize those checks.
There are also other third-party frameworks (or you could send health data to Application Insights, etc.), but using the built-in system ensures consistency and support with the ASP.NET Core ecosystem.
By following this guide, you should be able to set up robust health checks for your .NET application. This will give you and your team peace of mind that you can detect and react to issues promptly, ensuring high reliability for your services.