HTTPS, HSTS and Certificate Management

HTTPS is non-negotiable for any production web application โ€” it encrypts all traffic between clients and the server, preventing eavesdropping, man-in-the-middle attacks, and credential theft. ASP.NET Core has first-class HTTPS support: Kestrel (the built-in web server) handles TLS termination directly, a single middleware call enforces HTTPS redirects, and HSTS headers tell browsers to never connect over HTTP. Understanding how to configure HTTPS correctly for development, Docker, and production environments is foundational infrastructure knowledge for the Web API chapters.

HTTPS Configuration in ASP.NET Core

// โ”€โ”€ Program.cs โ€” HTTPS middleware setup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
var app = builder.Build();

// UseHsts โ€” adds Strict-Transport-Security header (production only)
// Tells browsers: only connect via HTTPS for the next 1 year
// Do NOT enable in Development โ€” breaks localhost HTTP development tooling
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

// UseHttpsRedirection โ€” redirects HTTP to HTTPS with 307 Temporary Redirect
// In production, use 301 Permanent Redirect (configured via options)
app.UseHttpsRedirection();

// โ”€โ”€ Configure HSTS in Program.cs build phase โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.Services.AddHsts(options =>
{
    options.Preload           = true;    // include in HSTS preload list
    options.IncludeSubDomains = true;    // apply to all subdomains
    options.MaxAge            = TimeSpan.FromDays(365);   // 1 year
    options.ExcludedHosts.Add("localhost");  // never HSTS on localhost
});

// โ”€โ”€ Configure HTTPS redirection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.Services.AddHttpsRedirection(options =>
{
    options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
    options.HttpsPort          = 443;
});
Note: HSTS (HTTP Strict Transport Security) is a response header that instructs browsers to only connect to your site via HTTPS for the specified duration (max-age). Once a browser receives the HSTS header, it will refuse HTTP connections for that duration โ€” even if the user types http://. The Preload option submits your domain to browser-maintained HSTS preload lists (embedded in Chrome, Firefox, Edge), meaning browsers enforce HTTPS even on the very first visit, before receiving the HSTS header. HSTS preloading is essentially irreversible โ€” only apply it when you are certain HTTPS will be maintained permanently.
Tip: In development, use dotnet dev-certs https --trust to create and trust a self-signed development certificate. This certificate is stored in the user’s certificate store and trusted by the OS, allowing HTTPS to work without browser warnings on localhost. On CI/CD pipelines, set the ASPNETCORE_Kestrel__Certificates__Default__Path and ASPNETCORE_Kestrel__Certificates__Default__Password environment variables to provide the certificate. For production, use a certificate from Let’s Encrypt, your cloud provider’s certificate management service, or a commercial CA.
Warning: When deploying behind a reverse proxy (Nginx, Azure Application Gateway, AWS ALB), TLS is typically terminated at the proxy level โ€” the proxy connects to Kestrel over HTTP internally. In this case, do not configure Kestrel with a TLS certificate โ€” configure it for HTTP on port 80, and set up the forwarded headers middleware to trust the proxy’s X-Forwarded-Proto header: app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto }). Without this, HTTPS redirects loop infinitely because Kestrel thinks the request is HTTP.

Kestrel HTTPS Configuration

// โ”€โ”€ appsettings.json โ€” Kestrel HTTPS endpoint โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// {
//   "Kestrel": {
//     "Endpoints": {
//       "Http": {
//         "Url": "http://*:80"
//       },
//       "Https": {
//         "Url": "https://*:443",
//         "Certificate": {
//           "Path": "/certs/blogapp.pfx",
//           "Password": "REPLACE_WITH_CERT_PASSWORD"
//         }
//       }
//     }
//   }
// }

// โ”€โ”€ Programmatic Kestrel HTTPS configuration โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(System.Net.IPAddress.Any, 443, listenOptions =>
    {
        listenOptions.UseHttps(httpsOptions =>
        {
            // Load certificate from Azure Key Vault
            httpsOptions.ServerCertificateSelector = (context, name) =>
                LoadCertificateFromVault(name);
        });
    });
});

Reverse Proxy with Forwarded Headers

// โ”€โ”€ Behind Nginx/load balancer: forward original scheme and IP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Must be BEFORE UseHttpsRedirection to prevent redirect loops!
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
                             | ForwardedHeaders.XForwardedProto;
    // Trust only your specific proxy โ€” do not use KnownAnyNetwork in production!
    options.KnownProxies.Add(System.Net.IPAddress.Parse("10.0.0.1"));
});

// In pipeline โ€” FIRST middleware, before UseHsts and UseHttpsRedirection
app.UseForwardedHeaders();
app.UseHsts();
app.UseHttpsRedirection();

Common Mistakes

Mistake 1 โ€” Using UseHsts in Development (breaks localhost tooling)

โŒ Wrong โ€” HSTS forces HTTPS on localhost, breaking HTTP development tools and test runners.

โœ… Correct โ€” wrap with if (!app.Environment.IsDevelopment()).

Mistake 2 โ€” Not configuring ForwardedHeaders behind a reverse proxy (infinite redirect loop)

โŒ Wrong โ€” Kestrel sees HTTP request, redirects to HTTPS, proxy forwards as HTTP again, infinite loop.

โœ… Correct โ€” configure ForwardedHeaders to trust the proxy and read X-Forwarded-Proto before UseHttpsRedirection.

🧠 Test Yourself

Your ASP.NET Core API is deployed behind an Azure Application Gateway (TLS termination at gateway). HTTP requests arrive at Kestrel. UseHttpsRedirection causes infinite redirect loops. Why, and how do you fix it?