# ASP.NET Core Health Check Implementations and Endpoints: A Complete Technical Guide

> Implement ASP.NET Core health checks easily. Explore middleware, IHealthCheck, and endpoints for robust application health monitoring. Get the complete technical guide.

- Repository: [.NET Platform/aspnetcore](https://github.com/dotnet/aspnetcore)
- Tags: deep-dive
- Published: 2026-06-20

---

**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`](https://github.com/dotnet/aspnetcore/blob/main/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`](https://github.com/dotnet/aspnetcore/blob/main/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`](https://github.com/dotnet/aspnetcore/blob/main/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`](https://github.com/dotnet/aspnetcore/blob/main/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`](https://github.com/dotnet/aspnetcore/blob/main/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`](https://github.com/dotnet/aspnetcore/blob/main/Program.cs)), calling `services.AddHealthChecks()` (implemented in [`src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs`](https://github.com/dotnet/aspnetcore/blob/main/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:

```csharp
// 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`:

```csharp
options.Predicate = r => r.Tags.Contains("ready");

```

### Custom Status Code Mapping

Map `Degraded` status to **202 Accepted** while keeping other defaults:

```csharp
options.ResultStatusCodes[HealthStatus.Degraded] = StatusCodes.Status202Accepted;

```

### Custom JSON Response Writer

Implement a custom writer to output specific health metrics:

```csharp
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.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/HealthChecks/HealthChecks/src/HealthCheckService.cs) executes all registered health checks concurrently and aggregates results into a `HealthReport`.
- **HealthCheckMiddleware** in [`src/Middleware/HealthChecks/src/HealthCheckMiddleware.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HealthChecks/src/HealthCheckMiddleware.cs) handles 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 `InvalidOperationException` for missing status mappings.
- **MapHealthChecks** extension methods in [`src/Middleware/HealthChecks/src/Builder/HealthCheckEndpointRouteBuilderExtensions.cs`](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HealthChecks/src/Builder/HealthCheckEndpointRouteBuilderExtensions.cs) integrate 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 via `HealthCheckOptions`.

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