Modern cloud-native applications must do more than simply start successfully—they need to continuously report whether they're healthy and ready to serve requests. Containers may start before dependencies are available, databases may become unreachable, or external services may fail unexpectedly.
ASP.NET Core provides a built-in Health Checks framework that integrates with orchestrators like Kubernetes, Docker, Azure App Service, and load balancers. Properly configured health checks help automate traffic routing, restarts, and monitoring.
In this article, you'll learn how to implement production-ready health checks in ASP.NET Core 11, differentiate between liveness and readiness probes, and understand how to validate your implementation using a structured testing methodology.
Note: This article focuses on implementation patterns and testing methodology. It does not include fabricated availability or performance metrics.
Why Health Checks Matter
Without health checks, infrastructure cannot determine whether an application can safely process requests.
Typical failure scenarios include:
Health checks allow monitoring systems to detect these failures automatically.
Understanding Health Check Types
| Probe | Purpose | Typical Response |
|---|
| Liveness | Is the application running? | Restart if unhealthy |
| Readiness | Can the application accept traffic? | Remove from load balancer |
| Startup | Has initialization completed? | Delay readiness until startup finishes |
Each probe serves a different purpose in cloud-native deployments.
Create the Project
dotnet new webapi -n HealthChecksDemo
Register Health Checks
Enable the Health Checks framework.
builder.Services.AddHealthChecks();
This registers the infrastructure required to expose health endpoints.
Map Health Check Endpoints
Expose a basic endpoint.
app.MapHealthChecks("/health");
Request:
GET /health
Successful response:
Healthy
Add a SQL Server Health Check
Install the package.
dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.SqlServer
Register the check.
builder.Services
.AddHealthChecks()
.AddSqlServer(
builder.Configuration.GetConnectionString("Default"));
If SQL Server becomes unavailable, the health endpoint reports an unhealthy state.
Add a Redis Health Check
Install:
dotnet add package AspNetCore.HealthChecks.Redis
Configure:
builder.Services
.AddHealthChecks()
.AddRedis(
"localhost:6379",
name: "redis");
This verifies Redis connectivity.
Add a Custom Health Check
Sometimes application-specific checks are required.
Example:
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class StorageHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var available = true;
if (!available)
return Task.FromResult(
HealthCheckResult.Unhealthy(
"Storage unavailable"));
return Task.FromResult(
HealthCheckResult.Healthy());
}
}
Register it.
builder.Services
.AddHealthChecks()
.AddCheck<StorageHealthCheck>(
"storage");
Separate Liveness and Readiness
Register tagged checks.
builder.Services
.AddHealthChecks()
.AddSqlServer(
connectionString,
tags: new[] { "ready" });
Expose separate endpoints.
app.MapHealthChecks("/health/live");
app.MapHealthChecks("/health/ready",
new HealthCheckOptions
{
Predicate = check =>
check.Tags.Contains("ready")
});
This separation allows infrastructure to distinguish between application availability and dependency readiness.
Return Detailed Health Information
Customize the response.
app.MapHealthChecks("/health",
new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType =
"application/json";
await context.Response.WriteAsJsonAsync(new
{
Status = report.Status.ToString(),
Results = report.Entries.Select(e => new
{
Name = e.Key,
Status = e.Value.Status.ToString()
})
});
}
});
Example response:
{
"status": "Healthy",
"results": [
{
"name": "sql",
"status": "Healthy"
},
{
"name": "redis",
"status": "Healthy"
}
]
}
Kubernetes Configuration
Example readiness probe.
readinessProbe:
httpGet:
path: /health/ready
port: 80
Example liveness probe.
livenessProbe:
httpGet:
path: /health/live
port: 80
Kubernetes automatically removes unhealthy pods from service and restarts failed containers when appropriate.
End-to-End Health Check Flow
A typical workflow is:
Application starts.
Health checks are registered.
Infrastructure calls /health/live.
Readiness probe verifies dependencies.
Healthy instances receive traffic.
Failed checks report unhealthy status.
Load balancer or orchestrator removes unhealthy instances.
Containers restart if necessary.
Built-in vs Custom Checks
| Health Check | Use Case |
|---|
| SQL Server | Database connectivity |
| Redis | Cache availability |
| RabbitMQ | Messaging infrastructure |
| URL Check | External APIs |
| Disk Storage | Storage capacity |
| Custom Check | Business-specific validation |
Combine built-in and custom checks for comprehensive monitoring.
Testing Methodology
Health checks validate operational readiness rather than application performance.
Test Environment
Maintain consistency for:
.NET SDK version
Infrastructure versions
Network configuration
Container runtime
Health check intervals
Test Scenarios
Evaluate:
Metrics to Observe
Collect:
Useful Tools
Useful tools include:
Kubernetes Dashboard
Azure Monitor
Prometheus
Grafana
Application Insights
dotnet-counters
Docker logs
Test failure scenarios in staging before deploying to production.
Best Practices
Separate liveness and readiness probes.
Keep liveness checks lightweight.
Include critical dependencies in readiness checks.
Return structured JSON responses.
Monitor health endpoint failures.
Avoid expensive database queries inside health checks.
Use tags to organize checks.
Regularly test failure and recovery scenarios.
Common Mistakes
| Mistake | Impact |
|---|
| Using one endpoint for all probes | Incorrect restart behavior |
| Performing expensive operations | Slow health responses |
| Including optional services in liveness | Unnecessary container restarts |
| Ignoring transient failures | False alarms |
| Exposing sensitive diagnostic information | Security risks |
| Never testing failure scenarios | Unexpected production behavior |
Troubleshooting
Health Endpoint Always Returns Unhealthy
Verify:
Kubernetes Restarts Containers Continuously
Review:
Readiness Never Becomes Healthy
Check:
FAQs
What is the difference between liveness and readiness?
Liveness determines whether the application is still running. Readiness determines whether it can safely handle incoming requests.
Should every dependency be included in health checks?
Only dependencies that are essential for serving requests should typically be included in readiness checks. Optional services may require different handling.
Can I create custom health checks?
Yes. Implement the IHealthCheck interface to validate application-specific resources or business requirements.
Are health checks expensive?
They should not be. Keep them lightweight and avoid long-running operations or complex queries.
Can monitoring tools consume health endpoints?
Yes. Platforms such as Kubernetes, Azure Monitor, Prometheus, and load balancers commonly use health endpoints to monitor application status.
Conclusion
Health checks are a fundamental part of building resilient ASP.NET Core applications. By continuously verifying application status and dependency availability, they enable orchestrators and monitoring platforms to make intelligent decisions about routing traffic, restarting services, and detecting failures.
Combining built-in health checks with custom validations, separating liveness from readiness, and regularly testing failure scenarios ensures your applications remain reliable and production-ready across modern cloud environments.