Cloud and IIS Deployment
Kestrel runs your app, a proxy sits in front, and settings change per environment
Guess first
Pick an answer. You will see if you were right at the end of the lesson.
What is the role of Kestrel in an ASP.NET Core app?
Real-world analogy
What is it?
Deploying an ASP.NET Core app means publishing it and running it on a host. Often it sits behind a reverse proxy such as nginx or IIS. Kestrel is the cross-platform server inside your app. A proxy handles the public side and passes on headers with the original scheme and client address. On Windows with IIS, you install the Hosting Bundle and run the app through the ASP.NET Core Module. In the cloud you may use App Service or containers. Settings differ per environment, and you read logs to find out why a start fails.
Real-world relevance
A team deploys to IIS for the first time, and the site returns HTTP 500.30, In-Process Startup Failure. The app threw an exception on startup. The Application Event Log and the stdout log, switched on in web.config, show a missing connection string. Out-of-process, the same kind of failure shows as 502.5, Process Failure. Another team puts the API behind nginx, and login redirects start looping. The app still thinks every request is plain http, and adding forwarded headers fixes it. Both problems come from the hosting layer, not from the business code.
Key points
- Kestrel is the web server — Kestrel is the cross-platform server built into ASP.NET Core. It can face the internet alone. Often a reverse proxy such as nginx or IIS sits in front for TLS and routing.
- IIS needs the ASP.NET Core Module — The ASP.NET Core Module (ANCM) connects IIS to your app. The Hosting Bundle installs the .NET runtime and this module. Install IIS first, or repair the bundle afterwards.
- In-process and out-of-process — In-process, the default, runs the app inside the IIS worker process with IIS HTTP Server, not Kestrel. Out-of-process runs the app on Kestrel in its own process, and IIS forwards requests to it.
- App pool: No Managed Code — ASP.NET Core does not load the old .NET Framework CLR. So set the pool's .NET CLR version to "No Managed Code", which Microsoft recommends. The module boots the modern .NET runtime itself.
- Publish, then web.config — Run dotnet publish -c Release. Framework-dependent output needs the runtime on the server; self-contained output carries it and is larger. Publish also writes a web.config whose aspNetCore element tells the module how to start the app.
- Cloud options — Azure App Service runs your app without you managing a server. Containers on a cloud service give you the same image everywhere, with more setup.
- Configuration per environment — Set ASPNETCORE_ENVIRONMENT and app settings on the host, not in the code. Keep secrets in a store such as Azure Key Vault, not in appsettings.json.
- Health checks — Add AddHealthChecks and MapHealthChecks to expose an endpoint. Load balancers and monitors call it to see if the app is up.
- Forwarded headers behind a proxy — Behind nginx, the proxy talks to your app over plain http, so the app loses the original scheme and IP. UseForwardedHeaders reads the X-Forwarded headers to restore them. Under IIS, the module and IIS integration handle this for you.
Quick check
Answer from memory — scrolling up first makes it stick less.
Which .NET CLR version does Microsoft recommend for an ASP.NET Core app's IIS application pool?
Behind a reverse proxy, the app sees requests as plain http. Which middleware helps restore the original scheme?
Code example
using System.Net;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// Trust only the proxy you know; by default only loopback proxies are trusted
options.KnownProxies.Add(IPAddress.Parse("10.0.0.5"));
});
var app = builder.Build();
app.UseForwardedHeaders(); // early, before middleware that reads the scheme
app.UseHttpsRedirection();
app.MapHealthChecks("/health");
app.MapGet("/", () => "Shop API is running");
app.Run();Line-by-line walkthrough
- 1. This is a fragment of Program.cs. The using lines bring in IPAddress and the forwarded headers types.
- 2. AddHealthChecks registers the health check services.
- 3. Configure sets which X-Forwarded headers to read: the client address and the original scheme. KnownProxies lists the one proxy we trust.
- 4. UseForwardedHeaders is called early, so middleware after it sees the real scheme and not plain http.
- 5. UseHttpsRedirection then works with the restored scheme.
- 6. MapHealthChecks exposes a /health endpoint for load balancers and monitors. MapGet adds a simple root route, and Run starts the app.
Spot the bug
var builder = WebApplication.CreateBuilder(args);
builder.Configuration["https_port"] = "443";
var app = builder.Build();
// Runs behind nginx, which terminates TLS and forwards plain http
app.UseHttpsRedirection();
app.MapGet("/", () => "ok");
app.Run();Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Host ASP.NET Core on Windows with IIS (Microsoft Learn)
- Configure ASP.NET Core to work with proxy servers and load balancers (Microsoft Learn)
- Troubleshoot ASP.NET Core on Azure App Service and IIS (Microsoft Learn)
Lock it in