A complete guide to implementing health checks in ASP.NET Core — covering liveness probes, readiness probes, custom
IHealthCheckimplementations, and a rich JSON detail endpoint.
If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content — all for the price of a coffee.
👉 Join CodingDroplets on Patreon
Prefer a one-time tip? Buy us a coffee ☕
- How to implement custom
IHealthCheckclasses for database, external service, and memory checks - The difference between liveness and readiness probes — and why they matter in Kubernetes and cloud deployments
- How to register and tag health checks to control which probes run which checks
- How to build a rich JSON
/healthz/detailendpoint with structured diagnostic data - How to configure custom HTTP status codes for each health state (Healthy, Degraded, Unhealthy)
- How to write integration tests for health endpoints using
WebApplicationFactory<T>
HTTP Request
│
▼
┌─────────────────────────────────────────────────────┐
│ ASP.NET Core Pipeline │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Health Check Endpoints │ │
│ │ │ │
│ │ GET /healthz → All checks (plain) │ │
│ │ GET /healthz/live → "live" tag only │ │
│ │ GET /healthz/ready → "ready" tag only │ │
│ │ GET /healthz/detail → All checks (JSON) │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Database │ │ External │ │ Memory │ │
│ │HealthCheck │ │ Service │ │ HealthCheck │ │
│ │ │ │HealthChk │ │ │ │
│ │ tag: ready │ │tag:ready │ │ tag: live │ │
│ └─────────────┘ └──────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────┘
Result: HealthCheckResponse (JSON)
├── status: "Healthy" | "Degraded" | "Unhealthy"
├── checkedAt: UTC timestamp
├── totalDurationMs: number
└── checks[]: name, status, description, durationMs, tags, data
| Check | Interface | Tags | Failure Status | Purpose |
|---|---|---|---|---|
DatabaseHealthCheck |
IHealthCheck |
ready, database |
Unhealthy | Verifies DB connectivity |
ExternalServiceHealthCheck |
IHealthCheck |
ready, external |
Degraded | Pings downstream HTTP service |
MemoryHealthCheck |
IHealthCheck |
live, memory |
Degraded | Monitors GC memory allocation |
| Endpoint | Checks Run | Response Format | Use Case |
|---|---|---|---|
GET /healthz |
All | Plain text | Simple status |
GET /healthz/live |
live tagged |
Plain text | Kubernetes liveness probe |
GET /healthz/ready |
ready tagged |
Plain text | Kubernetes readiness probe |
GET /healthz/detail |
All | Rich JSON | Dashboards, alerting, debugging |
dotnet-health-checks-api/
├── dotnet-health-checks-api.sln
│
├── HealthChecksApi/ ← Main Web API project
│ ├── HealthChecks/
│ │ ├── DatabaseHealthCheck.cs ← IHealthCheck: DB connectivity
│ │ ├── ExternalServiceHealthCheck.cs ← IHealthCheck: downstream HTTP service
│ │ └── MemoryHealthCheck.cs ← IHealthCheck: process memory usage
│ ├── Models/
│ │ └── HealthCheckResponse.cs ← Response models for /healthz/detail
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── Program.cs ← Service registration + endpoint mapping
│ ├── appsettings.json
│ └── appsettings.Development.json
│
└── HealthChecksApi.Tests/ ← xUnit test project
├── HealthChecks/
│ ├── DatabaseHealthCheckTests.cs ← Unit tests for DatabaseHealthCheck
│ └── MemoryHealthCheckTests.cs ← Unit tests for MemoryHealthCheck
└── Integration/
└── HealthEndpointTests.cs ← Integration tests for all endpoints
| Requirement | Version |
|---|---|
| .NET SDK | 8.0 or later (sample uses 10.0) |
| IDE | Visual Studio 2022+ / VS Code / Rider |
| Database | Not required — DB check is simulated (swap in real connection for production) |
# 1. Clone the repository
git clone https://github.com/codingdroplets/dotnet-health-checks-api.git
cd dotnet-health-checks-api
# 2. Build the solution
dotnet build -c Release
# 3. Run the API
cd HealthChecksApi
dotnet run
# 4. Test the health endpoints
curl http://localhost:5289/healthz
curl http://localhost:5289/healthz/live
curl http://localhost:5289/healthz/ready
curl http://localhost:5289/healthz/detailVisual Studio users: press F5 — the browser will open Swagger UI automatically.
Each health check implements the IHealthCheck interface with a single method:
public class DatabaseHealthCheck : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
// Check database connectivity
// Return Healthy, Degraded, or Unhealthy
return HealthCheckResult.Healthy("Database is reachable.", data: new Dictionary<string, object>
{
{ "latencyMs", 5 }
});
}
}Tags let you group checks for liveness vs. readiness probes:
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>(
name: "database",
failureStatus: HealthStatus.Unhealthy,
tags: ["ready", "database"])
.AddCheck<MemoryHealthCheck>(
name: "memory",
failureStatus: HealthStatus.Degraded,
tags: ["live", "memory"]);Filter which checks run on each endpoint using tag predicates:
// Liveness probe — only "live" checks
app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live"),
ResultStatusCodes =
{
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});
// Readiness probe — only "ready" checks
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResultStatusCodes =
{
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});Use a custom ResponseWriter to return structured JSON:
app.MapHealthChecks("/healthz/detail", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var response = new HealthCheckResponse
{
Status = report.Status.ToString(),
CheckedAt = DateTime.UtcNow,
Checks = report.Entries.Select(e => new HealthCheckEntry
{
Name = e.Key,
Status = e.Value.Status.ToString(),
Description = e.Value.Description,
DurationMs = e.Value.Duration.TotalMilliseconds,
Tags = e.Value.Tags,
Data = e.Value.Data.Count > 0 ? e.Value.Data : null
})
};
await context.Response.WriteAsync(JsonSerializer.Serialize(response, ...));
}
});| Method | Endpoint | Description | Healthy Status | Unhealthy Status |
|---|---|---|---|---|
GET |
/healthz |
Overall health (all checks, plain text) | 200 OK |
503 Service Unavailable |
GET |
/healthz/live |
Liveness probe (live tag only) |
200 OK |
503 Service Unavailable |
GET |
/healthz/ready |
Readiness probe (ready tag only) |
200 OK |
503 Service Unavailable |
GET |
/healthz/detail |
Rich JSON with all check details | 200 OK |
503 Service Unavailable |
{
"status": "Healthy",
"checkedAt": "2026-03-28T06:00:00Z",
"totalDurationMs": 18.42,
"checks": [
{
"name": "database",
"status": "Healthy",
"description": "Database is reachable and responding.",
"durationMs": 6.1,
"tags": ["ready", "database"],
"data": {
"check": "database",
"status": "healthy",
"latencyMs": 5
}
},
{
"name": "memory",
"status": "Healthy",
"description": "Memory usage is normal: 32.14 MB.",
"durationMs": 0.3,
"tags": ["live", "memory"],
"data": {
"check": "memory",
"allocatedMB": 32.14
}
}
]
}# Run all tests
dotnet test -c Release
# Run with verbose output
dotnet test -c Release --logger "console;verbosity=detailed"| Test Class | Type | Tests | Covers |
|---|---|---|---|
DatabaseHealthCheckTests |
Unit | 3 | DB check: healthy, degraded (no conn string), data keys |
MemoryHealthCheckTests |
Unit | 3 | Memory check: normal usage, thresholds in data, check key |
HealthEndpointTests |
Integration | 7 | All endpoints: status codes, JSON format, field presence |
| Total | 13 |
| Concept | Liveness Probe | Readiness Probe |
|---|---|---|
| Question | "Is the process alive?" | "Is it ready to serve traffic?" |
| Failure action | Container is restarted | Pod is removed from load balancer |
| Checks included | Lightweight (memory, CPU) | Dependency checks (DB, APIs) |
| Kubernetes config | livenessProbe |
readinessProbe |
| Endpoint | /healthz/live |
/healthz/ready |
| Status | Meaning | HTTP Code |
|---|---|---|
Healthy |
Everything is fine | 200 OK |
Degraded |
Working but below optimal | 200 OK |
Unhealthy |
Critical failure | 503 Service Unavailable |
Returning 200 when the database is down hides the failure from Kubernetes, load balancers, and monitoring tools. Proper health checks enable:
- Self-healing — Kubernetes restarts unhealthy pods automatically
- Zero-downtime deployments — readiness probes prevent traffic before the app is ready
- Observability — monitoring tools can alert on degraded states before they become failures
- ASP.NET Core 10 — Web API framework
- .NET 10 — Runtime (compatible with .NET 8+)
- Microsoft.Extensions.Diagnostics.HealthChecks — Built-in health check framework
- xUnit — Test framework
- Microsoft.AspNetCore.Mvc.Testing — Integration testing with
WebApplicationFactory<T>
- Health checks in ASP.NET Core — Microsoft Docs
- IHealthCheck Interface — Microsoft Docs
- Kubernetes Liveness and Readiness Probes
This project is licensed under the MIT License.
| Platform | Link |
|---|---|
| 🌐 Website | https://codingdroplets.com/ |
| 📺 YouTube | https://www.youtube.com/@CodingDroplets |
| 🎁 Patreon | https://www.patreon.com/CodingDroplets |
| ☕ Buy Me a Coffee | https://buymeacoffee.com/codingdroplets |
| 💻 GitHub | http://github.com/codingdroplets/ |
Want more samples like this? Support us on Patreon or buy us a coffee ☕ — every bit helps keep the content coming!