Framework Guide
C# .NET API Monitoring
Set up health check endpoints and uptime monitoring for your ASP.NET Core application. Covers built-in health checks, custom implementations, database checks, and external monitoring with UptimeSignal.
Why Monitor Your .NET API?
ASP.NET Core applications can fail in subtle ways that don't crash the process. Database connections exhaust, downstream services time out, or memory pressure causes GC pauses. External monitoring catches these before users file support tickets.
- Database connection pool exhaustion -- All connections are in use and new requests queue indefinitely
- GC pressure and memory leaks -- Large object heap fragmentation causes long GC pauses that freeze requests
- Downstream service failures -- HttpClient calls to external APIs time out and cascade through your app
- Thread pool starvation -- Blocking async-over-sync calls exhaust the thread pool, making the app unresponsive
Built-in Health Checks (Minimal API)
ASP.NET Core ships with Microsoft.Extensions.Diagnostics.HealthChecks built into the framework. No extra NuGet packages needed for basic health checks.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health");
app.MapGet("/", () => "Hello World");
app.Run();
This gives you a /health endpoint that returns 200 Healthy or 503 Unhealthy. The framework handles everything -- status codes, response text, and executing registered checks.
Built-in Health Checks (Controller-based)
If you're using the controller pattern with AddControllers(), health checks work the same way. The health check middleware is separate from MVC routing.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapControllers();
app.MapHealthChecks("/health");
app.Run();
Custom Health Checks with IHealthCheck
Implement IHealthCheck to create checks for anything -- external APIs, file systems, message queues, or business logic validations.
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class ExternalApiHealthCheck(
IHttpClientFactory httpClientFactory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
var client = httpClientFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(5);
var response = await client.GetAsync(
"https://api.stripe.com/v1/health",
cancellationToken);
return response.IsSuccessStatusCode
? HealthCheckResult.Healthy("Stripe API is reachable")
: HealthCheckResult.Degraded($"Stripe returned {response.StatusCode}");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(
"Cannot reach Stripe API",
exception: ex);
}
}
}
// Register in Program.cs
builder.Services.AddHttpClient();
builder.Services.AddHealthChecks()
.AddCheck<ExternalApiHealthCheck>(
"stripe_api",
failureStatus: HealthStatus.Degraded,
tags: ["external", "payment"]);
Database Health Checks
The community-maintained AspNetCore.HealthChecks.* packages provide ready-made checks for SQL Server, PostgreSQL, MySQL, Redis, and more.
Entity Framework Core
// Install: dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>(
name: "database",
failureStatus: HealthStatus.Unhealthy,
tags: ["db", "sql"]);
SQL Server
// Install: dotnet add package AspNetCore.HealthChecks.SqlServer
builder.Services.AddHealthChecks()
.AddSqlServer(
connectionString: builder.Configuration.GetConnectionString("Default")!,
name: "sqlserver",
tags: ["db", "sql"]);
PostgreSQL
// Install: dotnet add package AspNetCore.HealthChecks.NpgSql
builder.Services.AddHealthChecks()
.AddNpgSql(
connectionString: builder.Configuration.GetConnectionString("Postgres")!,
name: "postgresql",
tags: ["db", "postgres"]);
Multiple Checks Together
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>("database", tags: ["db"])
.AddRedis(builder.Configuration.GetConnectionString("Redis")!, tags: ["cache"])
.AddCheck<ExternalApiHealthCheck>("stripe", tags: ["external"])
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["self"]);
Returning JSON from Health Checks
By default, the health endpoint returns plain text (Healthy/Unhealthy). For monitoring tools like UptimeSignal, plain text with a 200/503 status code works perfectly. But if you want detailed JSON output for debugging, configure a custom response writer.
using System.Text.Json;
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
duration = report.TotalDuration.TotalMilliseconds,
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
duration = e.Value.Duration.TotalMilliseconds,
description = e.Value.Description,
error = e.Value.Exception?.Message
})
};
await context.Response.WriteAsync(
JsonSerializer.Serialize(result, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}));
}
});
Filtering Health Checks with Tags
Use tags to create separate endpoints for liveness and readiness -- critical for Kubernetes deployments and useful for targeted monitoring.
// Liveness: is the process alive? (no dependency checks)
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("self")
});
// Readiness: can it serve traffic? (all dependency checks)
app.MapHealthChecks("/readyz", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("db") || check.Tags.Contains("cache")
});
// Full: everything (for monitoring dashboards)
app.MapHealthChecks("/health");
Health Check UI
The AspNetCore.HealthChecks.UI package provides a dashboard for visualizing health check results across multiple services.
// Install:
// dotnet add package AspNetCore.HealthChecks.UI
// dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage
builder.Services.AddHealthChecksUI(options =>
{
options.SetEvaluationTimeInSeconds(30);
options.MaximumHistoryEntriesPerEndpoint(50);
options.AddHealthCheckEndpoint("API", "/health");
})
.AddInMemoryStorage();
var app = builder.Build();
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecksUI(options =>
{
options.UIPath = "/health-ui";
});
The UI is great for internal dashboards, but it only checks when someone views the page. For 24/7 monitoring and instant alerts, pair it with UptimeSignal.
Best Practices
- Keep checks fast -- Health checks should respond in under 500ms. Set timeouts on database and HTTP calls
- No auth on health endpoints -- Monitoring services need unauthenticated access. Use
.AllowAnonymous()if you have global auth - Separate liveness from readiness -- A process can be alive but not ready to serve traffic (e.g., during startup migrations)
- Don't over-check -- Only check dependencies your app truly needs. A degraded cache shouldn't make the whole app "unhealthy"
- Use HealthStatus.Degraded -- For non-critical issues. Return Unhealthy only when the app genuinely cannot serve requests
- Suppress health check logs -- Filter out health check requests from your logging to reduce noise
Suppress Health Check Logging
// In appsettings.json, suppress Microsoft health check logs:
{
"Logging": {
"LogLevel": {
"Microsoft.Extensions.Diagnostics.HealthChecks": "Warning"
}
}
}
// Or filter in middleware:
app.MapHealthChecks("/health")
.WithMetadata(new LoggingFilterAttribute());
Monitor Your .NET API with UptimeSignal
Once your health endpoint is deployed, add it to UptimeSignal for 24/7 external monitoring. UptimeSignal pings your endpoint on a schedule and alerts you via email when it fails.
- Deploy your health endpoint -- Make sure
/healthis publicly accessible and returns 200 when healthy - Sign up at UptimeSignal -- Create a free account at uptimesignal.io
- Add your monitor -- Enter your health check URL (e.g.,
https://api.yourapp.com/health) - Set your interval -- Free tier checks every 5 minutes. Pro tier ($15/mo) checks every minute
- Get alerted -- Receive email notifications when your API goes down and when it recovers
UptimeSignal checks your endpoint from outside your infrastructure, catching issues that internal monitoring misses -- DNS failures, SSL expiration, load balancer misconfigurations, and complete infrastructure outages.