Skip to content

Instantly share code, notes, and snippets.

@devops-school
Created December 2, 2025 06:33
Show Gist options
  • Select an option

  • Save devops-school/33b3d293dbc7cc4180987615fee41683 to your computer and use it in GitHub Desktop.

Select an option

Save devops-school/33b3d293dbc7cc4180987615fee41683 to your computer and use it in GitHub Desktop.
DOTNET: Kestrel-only / ASP.NET Core hosting-side performance techniques

1. Optimize Kestrel Endpoints & Protocols

  • 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 / CreateHostBuilder using UseKestrel + 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"
          }
        }
      }

2. Tune Kestrel Connection & Request Limits

  • 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
      });

3. Optimize Kestrel Keep-Alive & Timeouts

  • 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));
      });

4. Reduce Middleware Pipeline Overhead

  • 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.cs when 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 UseSession if not required; no UseCors globally if only few endpoints need it (use RequireCors instead).


5. Enable Response Compression for Text Content

  • 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.).


6. Use Caching (Memory / Distributed) Behind Kestrel

  • 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();
              });
          }
      }

7. Use Async All the Way (Avoid Blocking Calls)

  • 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

8. Optimize JSON Serialization & DTOs

  • 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.


9. Use Server GC & Proper Runtime Config

  • 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.json or appsettings via environment:

      {
        "runtimeOptions": {
          "configProperties": {
            "System.GC.Server": true
          }
        }
      }
    • Or set environment variable:

      • COMPlus_gcServer=1

10. Minimize Logging Overhead in Hot Paths

  • What Reduce noisy logs in hot code paths; set log levels appropriately (e.g., Information or Warning in production, not Debug/Trace for 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);
      }

11. Optimize Static File Serving (If Kestrel Serves Static Files)

  • 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.


12. Use Minimal APIs / Lean Endpoints for High-Throughput APIs

  • 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();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment