ASP.NET Core Health Check Implementations and Endpoints: A Complete Technical Guide
ASP.NET Core exposes application health states through a composable middleware pipeline that aggregates IHealthCheck implementations into HTTP endpoints with configurable status codes and response formats.
The dotnet/aspnetcore repository provides a built-in health-checking system that enables applications to signal their readiness and liveness to orchestrators and load balancers. This lightweight framework integrates directly with the dependency-injection container and endpoint routing system to provide real-time health status over HTTP.
Core Components of the Health Check System
HealthCheckService
The HealthCheckService class located in src/HealthChecks/HealthChecks/src/HealthCheckService.cs serves as the central abstraction for executing health checks. This singleton service aggregates all IHealthCheck instances registered in the DI container and returns a HealthReport containing the aggregated status and individual check results.
HealthCheckMiddleware
The HealthCheckMiddleware in src/Middleware/HealthChecks/src/HealthCheckMiddleware.cs acts as the HTTP pipeline entry point. When a request hits a mapped health endpoint, this middleware invokes HealthCheckService.CheckHealthAsync, maps the resulting HealthStatus to an HTTP status code, applies cache-control headers, and delegates response formatting to a writer function.
HealthCheckOptions
Configuration behavior is controlled by HealthCheckOptions defined in src/Middleware/HealthChecks/src/HealthCheckOptions.cs. This configuration object holds:
- A filter predicate to selectively run checks based on registration metadata
- A dictionary mapping
HealthStatusvalues to HTTP status codes - A response writer delegate for custom output formatting
- The
AllowCachingResponsesflag (defaultfalse) to control cache-header suppression
Endpoint Mapping and Response Writers
The MapHealthChecks extension methods in src/Middleware/HealthChecks/src/Builder/HealthCheckEndpointRouteBuilderExtensions.cs wire the middleware into the endpoint routing system. These methods verify that HealthCheckService is registered, create a pipeline with the supplied HealthCheckOptions, and register the route.
For response formatting, HealthCheckResponseWriters in src/Middleware/HealthChecks/src/HealthCheckResponseWriters.cs provides the default WriteMinimalPlaintext method, which outputs the status string (Healthy, Degraded, or Unhealthy) as plain text.
How the Health Check Pipeline Works
The health check system follows a six-step execution flow:
-
Registration – In
ConfigureServices(orProgram.cs), callingservices.AddHealthChecks()(implemented insrc/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs) registers theHealthCheckServiceas a singleton and provides theIHealthChecksBuilderfluent API for adding checks viabuilder.AddCheck<T>(). -
Endpoint Mapping – In the request pipeline,
app.MapHealthChecks("/health")creates anIApplicationBuilderthat injectsHealthCheckMiddlewarewith the suppliedHealthCheckOptions. -
Execution – When a request hits the endpoint, the middleware invokes
HealthCheckService.CheckHealthAsync. The service runs eachIHealthCheckimplementation concurrently (optionally filtered byHealthCheckOptions.Predicate) and aggregates the results into aHealthReport. -
Result Handling – The middleware looks up the aggregated
HealthReport.StatusinHealthCheckOptions.ResultStatusCodes. If the mapping is missing, the validation logic throws anInvalidOperationException. -
Response Generation – By default, the middleware uses
HealthCheckResponseWriters.WriteMinimalPlaintext, writing the status string as plain text. A custom writer can be supplied to emit JSON, XML, or other formats. -
Caching Control – When
AllowCachingResponsesisfalse(the default), the middleware addsCache-Control: no-store, no-cache,Pragma: no-cache, and an expiredExpiresheader to prevent intermediate proxies from caching health results.
Implementing Health Check Endpoints
The following minimal API example demonstrates the complete setup according to the dotnet/aspnetcore source implementation:
// Program.cs (Minimal API style)
var builder = WebApplication.CreateBuilder(args);
// 1. Register health checks
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("db")
.AddCheck<DiskSpaceHealthCheck>("disk");
// 2. Optional: customize options
var healthOptions = new HealthCheckOptions
{
// Run only checks whose name starts with "db"
Predicate = registration => registration.Name.StartsWith("db"),
// Return 200 for Healthy/Degraded, 503 for Unhealthy (default)
ResultStatusCodes = new Dictionary<HealthStatus, int>
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status200OK,
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
},
// Write JSON response instead of plain text
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var json = System.Text.Json.JsonSerializer.Serialize(report);
await context.Response.WriteAsync(json);
},
// Allow caching for faster health-check probes (usually false)
AllowCachingResponses = false
};
var app = builder.Build();
// 3. Map the endpoint
app.MapHealthChecks("/health", healthOptions);
app.Run();
Filtering Checks by Tags
To run only checks marked with specific tags, configure the predicate in HealthCheckOptions:
options.Predicate = r => r.Tags.Contains("ready");
Custom Status Code Mapping
Map Degraded status to 202 Accepted while keeping other defaults:
options.ResultStatusCodes[HealthStatus.Degraded] = StatusCodes.Status202Accepted;
Custom JSON Response Writer
Implement a custom writer to output specific health metrics:
options.ResponseWriter = async (ctx, report) =>
{
ctx.Response.ContentType = "application/json";
var payload = new {
status = report.Status.ToString(),
total = report.TotalDuration.TotalMilliseconds
};
await ctx.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(payload));
};
Summary
- HealthCheckService in
src/HealthChecks/HealthChecks/src/HealthCheckService.csexecutes all registered health checks concurrently and aggregates results into aHealthReport. - HealthCheckMiddleware in
src/Middleware/HealthChecks/src/HealthCheckMiddleware.cshandles the HTTP request pipeline, translating health statuses to HTTP response codes and managing cache headers. - HealthCheckOptions configures filtering, status code mapping, and response formatting, with validation that throws
InvalidOperationExceptionfor missing status mappings. - MapHealthChecks extension methods in
src/Middleware/HealthChecks/src/Builder/HealthCheckEndpointRouteBuilderExtensions.csintegrate the middleware with the endpoint routing system. - By default, responses include cache-prevention headers (
Cache-Control: no-store) and plain-text output, but both behaviors are fully customizable viaHealthCheckOptions.
Frequently Asked Questions
How do I register a custom health check in ASP.NET Core?
Call services.AddHealthChecks() in your service configuration, then chain AddCheck<T>() where T implements IHealthCheck. This registers the check in the DI container via HealthCheckRegistration, making it available to HealthCheckService when the endpoint is hit.
What is the difference between HealthCheckService and HealthCheckMiddleware?
HealthCheckService is the abstraction that executes health check logic and runs in the DI container, while HealthCheckMiddleware is the HTTP pipeline component that handles the request/response semantics. The middleware calls the service to get the health report, then maps that report to HTTP status codes and formats the output.
How do I prevent health check responses from being cached?
Set HealthCheckOptions.AllowCachingResponses = false (the default). When disabled, the middleware automatically adds Cache-Control: no-store, no-cache, Pragma: no-cache, and an expired Expires header to prevent intermediate proxies from caching health results that might indicate stale system states.
Can I map multiple health check endpoints with different configurations?
Yes. Call app.MapHealthChecks() multiple times with different route patterns and distinct HealthCheckOptions instances. Each endpoint can use a unique predicate to filter specific checks (e.g., /health/live for liveness and /health/ready for readiness), allowing different orchestrators to query specific application states.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →