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 HealthStatus values to HTTP status codes
  • A response writer delegate for custom output formatting
  • The AllowCachingResponses flag (default false) 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:

  1. Registration – In ConfigureServices (or Program.cs), calling services.AddHealthChecks() (implemented in src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs) registers the HealthCheckService as a singleton and provides the IHealthChecksBuilder fluent API for adding checks via builder.AddCheck<T>().

  2. Endpoint Mapping – In the request pipeline, app.MapHealthChecks("/health") creates an IApplicationBuilder that injects HealthCheckMiddleware with the supplied HealthCheckOptions.

  3. Execution – When a request hits the endpoint, the middleware invokes HealthCheckService.CheckHealthAsync. The service runs each IHealthCheck implementation concurrently (optionally filtered by HealthCheckOptions.Predicate) and aggregates the results into a HealthReport.

  4. Result Handling – The middleware looks up the aggregated HealthReport.Status in HealthCheckOptions.ResultStatusCodes. If the mapping is missing, the validation logic throws an InvalidOperationException.

  5. 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.

  6. Caching Control – When AllowCachingResponses is false (the default), the middleware adds Cache-Control: no-store, no-cache, Pragma: no-cache, and an expired Expires header 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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →