Kestrel, proxies and deployment

ASP.NET Core · Expert · 15 min

Production incidents often come from hosting details: wrong client IPs, HTTPS loops, limits and shutdowns.

Kestrel is an async, event-driven server built on System.IO.Pipelines. It supports HTTP/1.1, HTTP/2 and HTTP/3 (QUIC), and TLS. Configure it in code (builder.WebHost.ConfigureKestrel) or in configuration under Kestrel:Endpoints; set URLs with ASPNETCORE_URLS or ASPNETCORE_HTTP_PORTS (container images default to port 8080).

Important limits (KestrelServerLimits): MaxRequestBodySize (about 28.6 MB default), MaxConcurrentConnections, request header limits, KeepAliveTimeout, and minimum data rates that defend against slow clients.

Behind a reverse proxy or load balancer (Nginx, YARP, Azure Front Door, Kubernetes ingress), the app sees the proxy's IP and plain HTTP. Use the forwarded headers middleware early in the pipeline so X-Forwarded-For and X-Forwarded-Proto update RemoteIpAddress and Request.Scheme. Only trust known proxies/networks (KnownProxies, KnownIPNetworks), otherwise clients can spoof their IP. Without this, UseHttpsRedirection can cause redirect loops and generated links use http.

Graceful shutdown: on SIGTERM the host stops accepting connections, lets in-flight requests finish, and stops hosted services, within ShutdownTimeout. Make sure your orchestrator's grace period is longer.

Containers: dotnet publish /t:PublishContainer builds an OCI image without a Dockerfile, using the official images, which run as a non-root user by default. Chiseled (distroless) base images reduce size and attack surface.

Example

using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(k =>
{
    k.Limits.MaxRequestBodySize = 10 * 1024 * 1024;     // 10 MB
    k.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(1);
    k.AddServerHeader = false;
});
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
    o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse("10.0.0.0/8"));   // trusted proxies only
});
builder.Services.Configure<HostOptions>(o => o.ShutdownTimeout = TimeSpan.FromSeconds(20));
var app = builder.Build();

app.UseForwardedHeaders();                               // first!
app.MapGet("/ip", (HttpContext ctx) => $"{ctx.Connection.RemoteIpAddress} via {ctx.Request.Scheme}");
app.Run();
// Build an image without a Dockerfile:
//   dotnet publish -t:PublishContainer -p:ContainerImageTags=1.0

Remember

  • Kestrel limits protect against oversized and slow requests
  • UseForwardedHeaders first, trusting only known proxy networks
  • Graceful shutdown drains requests within ShutdownTimeout
  • dotnet publish can build non-root container images directly

Watch out for

  • Trusting X-Forwarded-For from any source, letting clients fake their IP address.

Check yourself

  1. Behind a TLS-terminating proxy, why does UseHttpsRedirection loop without forwarded headers?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel