Scale and Monitor ASP.NET Core in Production

Khimananda Oli 8 min read Programming and Languages
Scale and Monitor ASP.NET Core in Production

By Khimananda Oli | Last reviewed: August 2026

You cannot reliably scale what you cannot measure, yet many teams attempt to grow their .NET services without a unified observability strategy. To successfully scale and monitor ASP.NET Core in production, you must couple horizontal pod autoscaling with deep application telemetry rather than relying on CPU thresholds alone. This guide covers the specific configuration patterns, OpenTelemetry instrumentation, and infrastructure adjustments required to keep .NET workloads performant under real-world load.

ASP.NET CoreApp + OTel SDKTraces/MetricsPrometheusMetrics StoreTempo / JaegerTrace BackendKEDA ScalerCustom MetricsHPA
High-level architecture to scale and monitor ASP.NET Core in production using OpenTelemetry, Prometheus, and KEDA-driven autoscaling.

How do you instrument ASP.NET Core for production observability?

Before you can scale intelligently, you need high-fidelity data. Relying solely on built-in Azure Application Insights or basic CloudWatch metrics often leaves gaps when running on Kubernetes or hybrid infrastructure. The industry standard in 2026 is native OpenTelemetry (OTel) instrumentation. This allows you to decouple your monitoring vendor from your codebase and export OpenTelemetry signals to any backend.

Configuring the OpenTelemetry SDK

Add the core OTel packages to your ASP.NET Core project. Avoid legacy diagnostic source adapters; use the official Microsoft.Extensions.Telemetry packages which are now fully integrated into the .NET runtime.

<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.10.*" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.*" />

In your Program.cs, register the telemetry services. Note that we explicitly enable the experimental activity source for HTTP client calls and configure the OTLP exporter via environment variables to keep secrets out of source control.

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing
            .AddAspNetCoreInstrumentation(options =>
            {
                options.RecordException = true;
                options.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health");
            })
            .AddHttpClientInstrumentation()
            .AddOtlpExporter();
    })
    .WithMetrics(metrics =>
    {
        metrics
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation()
            .AddOtlpExporter();
    });

A common mistake I see in audits is failing to propagate trace context across asynchronous boundaries or message queues. Ensure your messaging middleware (RabbitMQ, Kafka, SQS) has the corresponding OTel instrumentation package installed, or your traces will break at the service boundary. For deeper guidance on signal selection, review metrics, logs, and traces compared.

What are the best autoscaling strategies for .NET containers?

CPU-based scaling is insufficient for most ASP.NET Core applications because the .NET runtime manages threads and memory differently than Node.js or Go. A pod might be processing thousands of queued messages while sitting at only 30% CPU, causing massive latency spikes before a new replica ever spawns. You need to implement horizontal pod autoscaling based on actual workload saturation.

Using KEDA for Custom Metric Scaling

Kubernetes Event-Driven Autoscaling (KEDA) bridges the gap between Prometheus metrics and the Kubernetes HPA controller. Instead of waiting for resource exhaustion, KEDA queries your metric store directly.

  • Prometheus Scaler: Scale based on request rate, active connections, or custom business counters exposed by your app.
  • RabbitMQ/SQS Scaler: Scale based on queue depth, ensuring workers spin up before backlog becomes critical.
  • Cron Scaler: Pre-scale instances before known traffic peaks, such as end-of-month billing cycles common in Nepali fintech platforms.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: aspnet-order-service
spec:
  scaleTargetRef:
    name: order-service-deployment
  minReplicaCount: 2
  maxReplicaCount: 20
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus.monitoring:9090
      metricName: http_requests_per_second
      query: sum(rate(http_server_request_duration_seconds_count{job="order-service"}[1m]))
      threshold: "100"
      activationThreshold: "5"

The activationThreshold parameter is crucial here. It prevents flapping when traffic hovers near zero, keeping a minimum baseline without constantly scaling to zero unless explicitly configured. Always pair this with appropriate Kubernetes resource limits and requests to prevent noisy-neighbor issues during rapid scale-out events.

ASP.NET AppPrometheusKEDAK8s APIPush MetricsQuery RateReturn ValuePatch ReplicasNew Pods Scheduled
Sequence flow demonstrating how KEDA queries Prometheus to scale and monitor ASP.NET Core in production dynamically.

Which .NET runtime settings optimize container performance?

Running ASP.NET Core in Docker or Kubernetes requires specific runtime tuning. The CLR was originally designed for dedicated servers, not ephemeral containers with hard memory limits. Without adjustment, you risk OOM kills and GC pauses that destroy p99 latency.

Garbage Collection and Thread Pool Tuning

Set these environment variables in your Dockerfile or Kubernetes deployment manifest. They tell the runtime to respect container boundaries and optimize for throughput over raw latency.

Environment VariableRecommended ValuePurpose
DOTNET_GCHeapHardLimitPercent90Prevents GC from consuming 100% of container memory limit, leaving room for native allocations.
DOTNET_ThreadPool_ForceMinWorkerThreads32Reduces thread injection delay during sudden burst traffic. Default ramp-up is too slow for web APIs.
DOTNET_EnableDiagnostics0Disables debug/diag ports in production to reduce attack surface and overhead.
ASPNETCORE_FORWARDEDHEADERS_ENABLEDtrueEnsures correct client IP and scheme detection behind ingress controllers or reverse proxies.

For high-throughput microservices, consider enabling Server GC mode explicitly if it isn't already default in your base image. Workstation GC is insufficient for handling concurrent requests at scale. Also, ensure your container base image matches your target architecture exactly; running x64 .NET binaries on ARM64 nodes via emulation will negate all performance tuning.

How do you define meaningful SLIs for ASP.NET Core services?

Monitoring dashboards full of CPU graphs don't tell you if users are happy. To truly scale and monitor ASP.NET Core in production effectively, you must track Service Level Indicators (SLIs) that reflect user experience. Refer to the four golden signals of monitoring as your baseline framework.

Implementing Business-Centric Metrics

Beyond standard HTTP duration histograms, instrument domain-specific operations. Use the Meter API to create custom instruments that align with your SLOs.

private static readonly Meter _meter = new("OrderService", "1.0.0");
private static readonly Counter<long> _ordersProcessed = _meter.CreateCounter<long>(
    "orders.processed.total", 
    unit: "{order}", 
    description: "Total number of orders successfully processed");

private static readonly Histogram<double> _paymentDuration = _meter.CreateHistogram<double>(
    "payment.processing.duration", 
    unit: "ms", 
    description: "Time taken to complete payment gateway transaction");

// In your service method:
_ordersProcessed.Add(1, new KeyValuePair<string, object?>("payment_method", "esewa"));
_paymentDuration.Record(stopwatch.ElapsedMilliseconds);

These custom metrics feed directly into your KEDA scalers and Grafana alerts. When defining thresholds, always distinguish between burn-rate alerts (how fast you're consuming error budget) and symptom-based alerts (current failure rate). Symptom alerts page humans; burn-rate alerts drive capacity planning. For structured log correlation, follow structured logging best practices to ensure every log line includes trace IDs matching your metrics.

Reactive (CPU-Based)Scale event triggers AFTER latency spikeUser Impact ZoneProactive (KEDA + Custom)Pods ready BEFORE demand peaksPre-scale Trigger
Visual comparison of reactive CPU scaling versus proactive custom-metric scaling when you scale and monitor ASP.NET Core in production.

Production Readiness Checklist for ASP.NET Core

Scaling and monitoring are ongoing disciplines, not one-time setup tasks. Before declaring your system production-ready, verify these operational controls exist and function correctly under load testing.

  1. Health Probes Are Accurate: Your /health endpoint checks downstream dependencies (DB, cache, queue) but fails fast with a timeout. Never let a health check hang indefinitely.
  2. Graceful Shutdown Is Implemented: Register IHostApplicationLifetime.ApplicationStopping to drain active requests and complete background jobs before the container terminates. Set terminationGracePeriodSeconds in Kubernetes to match your longest expected request duration plus buffer.
  3. Secrets Are Externalized: No connection strings or API keys in appsettings.json. Use Azure Key Vault, AWS Secrets Manager, or Kubernetes Secrets mounted as volumes.
  4. Rate Limiting Is Active: Configure Microsoft.AspNetCore.RateLimiting middleware to protect downstream services from cascading failures during partial outages.
  5. Observability Pipeline Is Verified: Confirm traces flow end-to-end through your ingress, app, and external dependencies. Validate that metric cardinality stays bounded (avoid unbounded tag values like user IDs).

Next Steps for Reliable .NET Operations

To scale and monitor ASP.NET Core in production sustainably, treat observability configuration as first-class infrastructure code alongside your Terraform and Helm charts. Start by instrumenting your critical paths with OpenTelemetry today, then layer in KEDA scalers based on actual business demand rather than generic resource utilization. If your team needs help designing an audit-ready observability stack or optimizing .NET performance for compliance-sensitive environments, reach out to discuss your architecture. Proper instrumentation pays for itself in reduced incident time and confident scaling decisions.

Frequently Asked Questions

Use Kubernetes with Horizontal Pod Autoscaler targeting custom metrics like request queue depth. Configure sticky sessions only if necessary, and ensure session state is externalized to Redis or SQL Server for true stateless scaling across multiple pods.

Map the built-in health check middleware to /health and expose detailed readiness at /ready. Configure your load balancer to poll these endpoints every ten seconds, returning HTTP 200 only when dependencies like databases and caches are fully operational.

OpenTelemetry Collector with Prometheus and Grafana is the current standard. It provides vendor-neutral tracing, metrics, and logging without proprietary agent lock-in, integrating natively with ASP.NET Core diagnostics libraries for comprehensive observability across distributed microservices.

No, not if you externalize state. Store session data in Redis or a database so any instance can serve requests. Sticky sessions reduce resilience by binding users to specific servers, creating bottlenecks during deployments or node failures.

Enable response compression, use Span for buffer management, and configure Kestrel connection limits. Profile with dotnet-dump to identify large object heap fragmentation and optimize serialization paths to decrease garbage collection pressure under sustained load.

Synchronous database calls, unbounded memory caches, and missing connection pooling cause most issues. Async/await misuse blocks thread pool threads, while inadequate caching strategies force repeated expensive computations that prevent horizontal scaling from improving throughput effectively.

Enable EF Core logging filters for warnings and errors, then correlate slow queries via OpenTelemetry traces. Set command timeout thresholds and use interceptors to capture execution plans, identifying N+1 problems and missing indexes before they impact users.

App Service suits small-to-medium workloads with simpler operations. Kubernetes offers superior density, custom autoscaling, and multi-cloud portability for complex architectures. Choose based on team expertise and whether advanced orchestration justifies the operational overhead in 2026.

Use environment variables or external config stores like Azure App Configuration with refresh tokens. Avoid appsettings.json modifications requiring restarts. Implement IOptionsMonitor for hot-reloading settings so new instances inherit current values without deployment cycles or downtime.

Enforce Strict-Transport-Security, Content-Security-Policy, and X-Content-Type-Options via middleware. Centralize header policies in reverse proxies like YARP or ingress controllers to ensure consistent protection across all scaled instances without per-application configuration drift.

Capture dumps using dotnet-dump during spikes, then analyze thread stacks for blocking calls or regex backtracking. Check GC allocation rates with dotnet-counters to distinguish between compute-bound logic and garbage collection overhead causing CPU saturation.

Yes, gRPC reduces payload size and latency significantly compared to JSON over HTTP. Use it for service-to-service calls within trusted networks, keeping REST for public APIs. Ensure load balancers support HTTP/2 for proper gRPC routing.

Default ThreadPool settings usually suffice. Only adjust MinimumThreads if profiling shows thread starvation during burst traffic. Over-provisioning threads increases context switching overhead; let the runtime manage concurrency based on actual CPU cores allocated to the container.

Use the built-in RateLimiter middleware with partitioned strategies keyed by client IP or API key. For distributed scenarios, back rate limit counters with Redis to enforce global thresholds consistently across all instances rather than per-node limits.

Health checks often fail due to dependency timeouts or resource exhaustion during peak traffic. Increase check timeouts, add circuit breakers to downstream calls, and ensure health endpoints themselves are lightweight to avoid false negatives during scaling events.