Hosting Models: Kestrel, IIS & Reverse Proxies

15 questions found

What is Kestrel, and how does its role in ASP.NET Core hosting differ from a traditional web server like IIS?

Beginner
Kestrel is the default, cross-platform, high-performance web server built directly into ASP.NET Core, responsible for handling raw HTTP connections and requests -- unlike IIS (a full-featured Windows web server providing process management, SSL termination, static file serving, and more historically as the primary way to host .NET applications), Kestrel is deliberately lightweight and focused purely on serving the application, commonly used either standalone (directly exposed) or, in production, placed behind a reverse proxy for additional capabilities.
// Program.cs -- Kestrel is used automatically via WebApplication.CreateBuilder
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello from Kestrel");
app.Run();

// Kestrel listens directly on the configured port, e.g. http://localhost:5000
Real-world example A containerized microservice runs Kestrel directly as its web server inside a Docker container, with no IIS involved at all, since the container orchestration platform (Kubernetes) and its ingress controller handle routing and load balancing instead.

Common follow-ups: When would you run Kestrel directly without any reverse proxy in front of it?;What capabilities does IIS or a reverse proxy add that Kestrel alone doesn't provide?

Configuration & Options Pattern;HTTPS Certificates & Transport Security

What is the difference between the in-process and out-of-process hosting models when running an ASP.NET Core application under IIS?

Intermediate
In-process hosting runs the ASP.NET Core application directly inside the IIS worker process (w3wp.exe) via the AspNetCoreModuleV2, avoiding an extra network hop and providing better performance (the default and recommended model since .NET Core 3.0) -- out-of-process hosting instead runs Kestrel as a separate process, with IIS acting purely as a reverse proxy forwarding requests to it, useful in scenarios needing process isolation from IIS itself or when running on an older hosting model.
<!-- web.config -->
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
            hostingModel="inprocess" />  <!-- default, better performance -->

<!-- Alternative: out-of-process -->
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
            hostingModel="outofprocess" />
Real-world example A high-throughput internal API hosted on IIS explicitly confirms it's using in-process hosting (the default) to avoid the extra reverse-proxy hop and process overhead that out-of-process hosting would introduce, since raw request throughput is a priority.

Common follow-ups: What are the specific scenarios where out-of-process hosting would still be preferred despite the performance cost?;How do app pool recycling behaviors differ between the two models?

Diagnostics & Performance;Configuration & Options Pattern

How would you configure Kestrel to be production-ready when running standalone (without IIS or another reverse proxy in front of it), and what risks does this expose you to?

Advanced
Running Kestrel directly exposed to the internet requires manually configuring what a reverse proxy would otherwise provide: request size limits, connection/request timeouts, minimum data rates (to mitigate slow-loris style attacks), and header size limits -- Kestrel is capable of being production-ready standalone since ASP.NET Core 2.1+, but this configuration must be deliberate, and additional infrastructure-level protections (DDoS mitigation, TLS termination at a CDN edge) commonly provided by reverse proxies are then the application's own responsibility.
builder.WebHost.ConfigureKestrel(options => {
    options.Limits.MaxRequestBodySize = 30_000_000;
    options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
});
Real-world example A cloud-native service running Kestrel standalone behind only a cloud load balancer (no traditional reverse proxy) explicitly configures minimum data rate limits to defend against slow-loris connection-exhaustion attacks that a reverse proxy would otherwise have mitigated automatically.

Common follow-ups: What specific protections does a CDN or cloud load balancer provide that Kestrel's own configuration can't fully replace?;How do you decide whether standalone Kestrel is sufficient versus needing a dedicated reverse proxy?

HTTPS Certificates & Transport Security;Rate Limiting

How does ASP.NET Core determine the original client IP address and scheme (HTTP/HTTPS) when running behind a reverse proxy that terminates TLS and forwards requests, given the app only sees the proxy's connection?

Intermediate
Behind a reverse proxy, HttpContext.Connection.RemoteIpAddress and Request.Scheme by default reflect the proxy's own connection details, not the original client's -- the ForwardedHeadersMiddleware (UseForwardedHeaders()) reads standard X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers set by the proxy and rewrites these values to the actual original client information, which must be explicitly enabled and configured with the trusted proxy's IP range for security.
builder.Services.Configure<ForwardedHeadersOptions>(options => {
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.5"));  // trust only this specific proxy
});

var app = builder.Build();
app.UseForwardedHeaders();  // must run early in the pipeline, before other middleware relying on client IP/scheme
Real-world example An API behind an nginx reverse proxy enables ForwardedHeadersMiddleware configured with nginx's known internal IP, correctly recovering the true external client IP address for rate-limiting and audit logging purposes instead of seeing nginx's own internal IP for every single request.

Common follow-ups: What security risk exists if you trust X-Forwarded-For headers from untrusted or unconfigured sources?;Why must UseForwardedHeaders() run before other middleware that depend on the client IP or scheme?

CORS (Cross-Origin Resource Sharing);Security Headers Antiforgery & CSRF Protection

What is YARP (Yet Another Reverse Proxy), and how does it compare to nginx or IIS as a reverse proxy option for ASP.NET Core applications?

Advanced
YARP is Microsoft's own open-source, code-first reverse proxy toolkit built on ASP.NET Core itself, letting you build a fully customizable reverse proxy/API gateway in C# (with routing, load balancing, and transformation logic defined as .NET code and configuration) rather than a separate infrastructure component with its own configuration language (like nginx.conf) -- attractive when a team wants gateway logic that's version-controlled alongside application code, written in a familiar language, and easily extended with custom .NET middleware/plugins.
// Program.cs -- YARP as a reverse proxy defined entirely in C#/appsettings
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();
app.MapReverseProxy();

// appsettings.json
"ReverseProxy": {
  "Routes": { "route1": { "ClusterId": "cluster1", "Match": { "Path": "/api/{**catch-all}" } } },
  "Clusters": { "cluster1": { "Destinations": { "d1": { "Address": "https://backend:5001" } } } }
}
Real-world example A team building an API gateway in front of several internal microservices chooses YARP over nginx specifically because they want to write custom C# request transformation logic (adding internal auth headers, request shaping) directly as .NET middleware rather than learning and maintaining a separate nginx configuration language and Lua scripting.

Common follow-ups: What are YARP's current limitations compared to a mature, battle-tested option like nginx for extreme-scale scenarios?;How does YARP's load balancing and health-checking compare to nginx's built-in capabilities?

API Versioning;Configuration & Options Pattern

How do you configure Kestrel to listen on multiple endpoints (different ports, or both HTTP and HTTPS simultaneously)?

Intermediate
Kestrel's endpoint configuration (via ConfigureKestrel in code, or the Kestrel:Endpoints section in appsettings.json) lets you define multiple named endpoints, each with its own URL/port and optional per-endpoint HTTPS certificate configuration -- useful for scenarios like exposing both an HTTP endpoint (for a load balancer's health check) and an HTTPS endpoint (for actual traffic) simultaneously, or separating internal admin traffic on a different port from public API traffic.
// appsettings.json
"Kestrel": {
  "Endpoints": {
    "Http": { "Url": "http://*:5000" },
    "Https": { "Url": "https://*:5001", "Certificate": { "Path": "cert.pfx", "Password": "..." } }
  }
}

// Or in code
builder.WebHost.ConfigureKestrel(options => {
    options.ListenAnyIP(5000);
    options.ListenAnyIP(5001, o => o.UseHttps("cert.pfx", "password"));
});
Real-world example A service exposes a plain HTTP endpoint on port 5000 purely for a Kubernetes liveness probe (avoiding TLS handshake overhead for frequent internal health checks) while serving actual public API traffic over HTTPS on port 5001, both configured within the same Kestrel instance.

Common follow-ups: How do you configure a separate management/admin endpoint that's only accessible internally?;What's the interaction between Kestrel endpoint configuration and a reverse proxy handling the actual public-facing HTTPS termination?

HTTPS Certificates & Transport Security;Health Checks

How would you diagnose and resolve a scenario where an application works correctly when run directly with Kestrel locally but returns 502 Bad Gateway errors when deployed behind IIS or nginx in production?

Advanced
Common causes include: the reverse proxy's own configuration not correctly forwarding to Kestrel's actual listening port/address (mismatched port bindings), Kestrel crashing/failing to start due to a missing configuration only present via IIS/proxy environment (like a missing certificate), the ASP.NET Core Module (for IIS) not being installed or an outdated hosting bundle, or the app failing to bind because ASPNETCORE_URLS conflicts with what the proxy expects -- diagnosing typically starts with checking the application's own stdout/stderr logs (enabled via <aspNetCore stdoutLogEnabled="true" /> for IIS) to see if the app is crashing on startup entirely, rather than a genuine proxy misconfiguration.
<!-- web.config -- enable stdout logging to diagnose startup failures -->
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
            stdoutLogEnabled="true"
            stdoutLogFile=".\logs\stdout" />

# Check Windows Event Viewer's Application log for ASP.NET Core Module errors
# Check IIS's own HTTP error logs for the specific 502.x sub-status code, which narrows the cause
Real-world example A team debugging a production 502 error behind IIS discovers via enabled stdout logging that the app was actually crashing on startup due to a missing environment-specific configuration value only set in production, a root cause invisible from IIS's generic 502 error page alone.

Common follow-ups: What do the different IIS 502.x sub-status codes indicate about the specific failure type?;How does this diagnostic approach differ when the reverse proxy is nginx instead of IIS?

Diagnostics & Performance;Error Handling

What is the ASP.NET Core Module (ANCM), and what specific role does it play when hosting under IIS?

Intermediate
The ASP.NET Core Module (ANCM, specifically ANCM V2) is a native IIS module that acts as the bridge between IIS and an ASP.NET Core application -- for in-process hosting, it hosts the .NET runtime directly within the IIS worker process; for out-of-process hosting, it manages starting/monitoring/restarting the separate Kestrel process and proxies requests to it, additionally handling process lifecycle concerns like automatically restarting a crashed app.
<!-- web.config -- installed automatically by the .NET hosting bundle, referenced here -->
<system.webServer>
  <handlers>
    <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
  </handlers>
  <aspNetCore processPath="dotnet" arguments=".\MyApp.dll" hostingModel="inprocess" />
</system.webServer>
Real-world example An IIS-hosted application crashes due to an unhandled exception in a background thread, and the ASP.NET Core Module automatically detects the process failure and restarts it, restoring service without manual intervention, a resilience behavior IIS wouldn't provide for a non-ASP.NET-Core-aware process on its own.

Common follow-ups: What needs to be installed on a Windows Server for the ASP.NET Core Module to function (the hosting bundle)?;How does ANCM's process restart behavior interact with graceful shutdown of in-flight requests?

Background Tasks & Hosted Services;Error Handling

How would you configure Kestrel's connection limits and thread pool behavior to handle high-concurrency production traffic without exhausting server resources?

Advanced
Kestrel's Limits.MaxConcurrentConnections and MaxConcurrentUpgradedConnections cap the total simultaneous connections to prevent unbounded resource consumption under extreme load, while the .NET thread pool's own minimum thread count (ThreadPool.SetMinThreads) can be tuned to avoid a slow thread-pool-growth ramp-up causing artificial latency spikes at the very start of a traffic surge -- these settings should be tuned based on load testing against your specific application's actual resource consumption per connection/request, not applied as generic defaults.
builder.WebHost.ConfigureKestrel(options => {
    options.Limits.MaxConcurrentConnections = 1000;
    options.Limits.MaxConcurrentUpgradedConnections = 1000;  // e.g., WebSocket connections
});

// In Program.cs, before the host builds
ThreadPool.SetMinThreads(workerThreads: 200, completionPortThreads: 200);
Real-world example An API experiencing latency spikes at the very start of sudden traffic surges (correlated with slow thread pool growth) sets a higher ThreadPool minimum thread count after load testing confirmed the default gradual ramp-up was the specific cause of those initial-spike latencies.

Common follow-ups: What are the risks of setting MaxConcurrentConnections too low versus too high?;How do you determine the right MinThreads value through load testing rather than guessing?

Diagnostics & Performance;Rate Limiting

How does hosting an ASP.NET Core application in a Docker container change considerations around Kestrel configuration and reverse proxying compared to traditional VM or bare-metal hosting?

Intermediate
In a containerized deployment, Kestrel typically runs as the sole process inside each container listening on a fixed internal port, with the actual reverse proxying, load balancing, and TLS termination handled by external infrastructure (a Kubernetes Ingress controller, a cloud load balancer, or a service mesh sidecar like Envoy) rather than IIS or an in-container reverse proxy -- this shifts responsibility for concerns like connection limits and TLS to the surrounding orchestration layer, while the container itself usually just needs Kestrel configured for plain HTTP on a known internal port.
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV ASPNETCORE_URLS=http://+:8080
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "MyApp.dll"]

# Kubernetes Service/Ingress handles TLS termination and load balancing externally,
# container itself just exposes plain HTTP on 8080 internally
Real-world example A microservice deployed to Kubernetes runs Kestrel listening on plain HTTP internally, relying entirely on the cluster's Ingress controller for TLS termination and external routing, simplifying the container's own configuration compared to a traditional VM deployment that would need Kestrel or IIS to handle HTTPS directly.

Common follow-ups: What are the security implications of running plain HTTP inside the cluster network versus end-to-end encryption?;How does a service mesh sidecar proxy change this picture further?

HTTPS Certificates & Transport Security;Configuration & Options Pattern

Showing 1–10 of 15