-
What Configure Kestrel endpoints explicitly (ports, HTTPS, HTTP/2/HTTP/3), and disable protocols you don’t need.
-
Why
- Reduces overhead from unused protocols.
- Ensures TLS is offloaded correctly (if using reverse proxy) or configured efficiently when Kestrel terminates TLS.
- Helps control concurrent connections & resource usage.
-
Where / How
-
In
Program.cs/CreateHostBuilderusingUseKestrel+ConfigureKestrel:builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(5000, o => { o.Protocols = HttpProtocols.Http1AndHttp2; // or Http1, Http2, Http3 if configured }); });
-
Or
appsettings.json:"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5000", "Protocols": "Http1AndHttp2" } } }
-
-
What Adjust limits like MaxConcurrentConnections, MaxConcurrentUpgradedConnections (WebSockets), MaxRequestBodySize, RequestHeadersTimeout, etc.
-
Why
- Protects app from overload and slow clients.
- Prevents memory abuse from very large requests.
- Keeps thread pool & GC under control during spikes.
-
Where / How
-
In
ConfigureKestrel:builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxConcurrentConnections = 10000; options.Limits.MaxConcurrentUpgradedConnections = 1000; // e.g. WebSockets options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; // 50 MB options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(15); });
-
Per-request override (e.g., large upload endpoint):
app.MapPost("/upload", async context => { context.Features.Get<IHttpMaxRequestBodySizeFeature>()!.MaxRequestBodySize = 100 * 1024 * 1024; // handle upload });
-
-
What Configure KeepAliveTimeout, MinRequestBodyDataRate, and other timeout-related options for Kestrel.
-
Why
- Frees resources stuck on slow/unresponsive clients.
- Avoids too many idle connections consuming memory and sockets.
- Keeps throughput high under load.
-
Where / How
-
In
ConfigureKestrel:builder.WebHost.ConfigureKestrel(options => { options.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(120); options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(10)); options.Limits.MinResponseDataRate = new MinDataRate(bytesPerSecond: 240, gracePeriod: TimeSpan.FromSeconds(10)); });
-
-
What Use only necessary middleware, in the most efficient order. Remove/avoid heavy or unused middleware in the Kestrel pipeline.
-
Why
- Every middleware runs on every request → fewer = faster.
- Proper ordering reduces branching & repeated work.
- Especially important under high RPS.
-
Where / How
-
In
Program.cswhen building the HTTP pipeline:var app = builder.Build(); // GOOD: lightweight, ordered app.UseRouting(); app.UseAuthentication(); // only if needed app.UseAuthorization(); // only if needed app.UseResponseCompression(); // if enabled app.MapControllers();
-
Remove unused ones: e.g., no
UseSessionif not required; noUseCorsglobally if only few endpoints need it (useRequireCorsinstead).
-
-
What Add Response Compression middleware for text-based responses (JSON, HTML, CSS, JS).
-
Why
- Reduces response size drastically (often 70–90%).
- Huge throughput gain on limited bandwidth.
- Kestrel sends fewer bytes → faster responses & lower network cost.
-
Where / How
-
Register in
Program.cs:builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.MimeTypes = new[] { "text/plain", "text/html", "text/css", "application/javascript", "application/json", "application/xml" }; }); var app = builder.Build(); app.UseResponseCompression();
-
Make sure not to compress already-compressed formats (JPEG, PNG, MP4, etc.).
-
-
What Cache heavy computations and data (e.g., lookup lists, configuration, expensive API results) using IMemoryCache or IDistributedCache + Response Caching.
-
Why
- Reduces CPU and DB load.
- Kestrel responds quickly from memory instead of recomputing.
- Great for frequently requested read-mostly data.
-
Where / How
-
Register in
Program.cs:builder.Services.AddMemoryCache(); builder.Services.AddResponseCaching(); var app = builder.Build(); app.UseResponseCaching();
-
In your endpoint/controller:
public class ProductsController : ControllerBase { private readonly IMemoryCache _cache; public ProductsController(IMemoryCache cache) => _cache = cache; [HttpGet("products")] [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)] public async Task<IEnumerable<Product>> Get() { return await _cache.GetOrCreateAsync("products_cache", async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); // expensive DB call return await _repository.GetProductsAsync(); }); } }
-
-
What Make all I/O operations async (DB, HTTP calls, file I/O), and never block on async (
.Result,.Wait()). -
Why
- Kestrel threads stay free and handle more concurrent requests.
- Prevents thread pool starvation and long tail latencies.
- Crucial for high-concurrency APIs.
-
Where / How
-
Controller / minimal API example:
app.MapGet("/orders/{id}", async (int id, IOrdersRepo repo) => { var order = await repo.GetOrderAsync(id); // async return order is null ? Results.NotFound() : Results.Ok(order); });
-
Avoid:
var result = repo.GetOrderAsync(id).Result; // BAD: blocks thread
-
-
What Tune System.Text.Json options and DTO shapes (only needed fields, avoid deep object graphs).
-
Why
- JSON serialization/deserialization is often a major CPU cost.
- Smaller payloads + simpler models = faster responses and lower CPU.
-
Where / How
-
Configure JSON globally:
builder.Services .AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.JsonSerializerOptions.WriteIndented = false; // save bytes });
-
Use separate, lightweight DTOs rather than sending entire entities with unnecessary fields.
-
-
What Enable Server GC and, where appropriate, tweak GC settings using runtime configuration.
-
Why
- Server GC uses multiple threads per heap → better for multi-core servers.
- Lower latency and higher throughput for Kestrel-based apps under load.
-
Where / How
-
In
*.runtimeconfig.jsonorappsettingsvia environment:{ "runtimeOptions": { "configProperties": { "System.GC.Server": true } } } -
Or set environment variable:
COMPlus_gcServer=1
-
-
What Reduce noisy logs in hot code paths; set log levels appropriately (e.g.,
InformationorWarningin production, notDebug/Tracefor everything). -
Why
- Logging can be a major CPU and I/O cost.
- Synchronous logging can block; too much logging increases latency and reduces throughput.
-
Where / How
-
In
appsettings.Production.json:"Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }
-
Use structured, conditional logging:
if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Expensive log {data}", someHeavyData); }
-
-
What Use the Static Files middleware with proper cache headers and only for required paths.
-
Why
- Offloads repeated file reads via browser caching.
- Reduces CPU and disk I/O when many clients request the same assets.
-
Where / How
-
Register:
builder.Services.AddDirectoryBrowser(); // if needed var app = builder.Build(); app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = ctx => { ctx.Context.Response.Headers.CacheControl = "public,max-age=31536000"; // 1 year for versioned assets } });
-
If behind CDN or reverse proxy, serve static files from there instead and let Kestrel focus on dynamic traffic.
-
-
What For performance-critical HTTP APIs, use Minimal APIs or otherwise lean endpoints instead of heavy MVC stacks where possible.
-
Why
- Fewer abstractions and filters → lower per-request overhead.
- Especially beneficial in microservices with simple JSON I/O.
-
Where / How
-
Example in
Program.cs:var app = builder.Build(); app.MapGet("/ping", () => Results.Ok(new { status = "ok" })); app.Run();
-