HSTS in ASP.NET Core: Stop SSL Stripping and HTTP Downgrade Attacks

Most teams treat HTTPS as the finish line for transport security. You install a certificate, force HTTPS redirection, watch the padlock appear, and move on. In production I have watched that assumption quietly fail - because HTTPS on its own still leaves a narrow window an attacker can exploit before the first secure connection is ever established. The control that closes that window is HSTS (HTTP Strict Transport Security), and it is one of the cheapest, highest-leverage security headers you can ship in an ASP.NET Core API.
This guide covers what HSTS actually defends against, the vulnerable pattern most APIs ship with, and how to configure UseHsts correctly in modern ASP.NET Core. If you want the complete, annotated transport-security setup - HSTS, HTTPS redirection, the wider security-header set, and the middleware ordering that ties them together - the production-ready source is on Patreon, wired into a working API you can run and adapt rather than copy in pieces.
The Threat: SSL Stripping and Protocol Downgrade
Picture a user who types example.com into their browser, or clicks an old http:// link. The very first request leaves their machine as plain HTTP, before any TLS handshake happens. On a hostile network - public Wi-Fi, a compromised router, a malicious proxy - an attacker sitting in the middle can intercept that first request and simply never let it upgrade to HTTPS.
This is called SSL stripping (a classic man-in-the-middle downgrade). The attacker talks HTTPS to your server and plain HTTP to the victim, transparently relaying traffic while reading and modifying everything in clear text. The padlock the user expects never appears, but most people do not notice its absence. Credentials, session cookies, and API tokens all travel in the open.
The root problem is trust on first use. Your UseHttpsRedirection() middleware does redirect HTTP to HTTPS - but that redirect itself is an HTTP response an attacker can intercept and rewrite. Redirection alone cannot protect the connection that has not happened yet.
Why HTTPS Alone Is Not Enough
HTTPS guarantees that an established TLS connection is encrypted and authenticated. It says nothing about how the browser should behave before that connection exists, or what to do if a future connection is attempted over plain HTTP. That gap is exactly what a downgrade attack lives in.
HSTS closes the gap by moving the decision into the browser. When your server sends the Strict-Transport-Security response header, a conforming browser records a rule: for this domain, never use HTTP again, for the duration you specify. Every later request - even one the user typed as http:// - is rewritten to HTTPS by the browser itself, before it ever hits the network. There is no HTTP request left for an attacker to strip.
In short: HTTPS protects the connection, and HSTS protects the decision to connect securely in the first place.
The Vulnerable Pattern Most APIs Ship With
A surprising number of ASP.NET Core services go to production with HTTPS redirection enabled and HSTS quietly missing. The pipeline looks secure at a glance:
app.UseHttpsRedirection();
app.MapControllers();
Redirection is present, the certificate is valid, and a quick test in the browser shows https://. But there is no Strict-Transport-Security header on any response, so a returning visitor's browser has no standing instruction to refuse HTTP. The first request of every session is still strippable. This pattern passes a casual review and fails a real threat model.
Configuring HSTS the Right Way in ASP.NET Core
AddHsts has been part of ASP.NET Core since 2.1 and is unchanged through .NET 10, so this applies to every supported version. Configure the options in the service container rather than passing them inline:
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
Then apply the middleware - but only outside Development, because forcing HSTS on localhost can cache a rule that interferes with local HTTP tooling:
if (!app.Environment.IsDevelopment())
app.UseHsts();
Two ordering rules matter. UseHsts() belongs early in the pipeline, and it should sit alongside UseHttpsRedirection() so that redirection upgrades the connection and HSTS instructs the browser to keep it upgraded. The default ASP.NET Core template deliberately skips UseHsts in Development for the reason above - that is a feature, not an omission to "fix" by removing the environment check.
Choosing Your HSTS Options
The header is simple, but each option carries a real operational trade-off:
MaxAge- how long the browser enforces HTTPS for your domain. One year (TimeSpan.FromDays(365)) is the widely accepted production value. Start lower (for example a few days) when first rolling out, confirm nothing breaks, then raise it.IncludeSubDomains- extends the rule to every subdomain. Powerful, but only enable it once you are certain every subdomain (including internal tools and staging hosts on the same domain) can serve HTTPS. One HTTP-only subdomain becomes unreachable.Preload- signals that you want the domain baked into the browsers' built-in HSTS preload list, so HTTPS is enforced even on a user's very first visit. This is the strongest protection and also the hardest to reverse.
The Preload List: Powerful but Hard to Undo
Setting Preload = true only emits the header flag - you still have to submit the domain at the official preload site, and it requires IncludeSubDomains plus a long max-age. The trade-off that bit a team I worked with: removal from the preload list is slow and ships only with future browser releases, so a domain accidentally preloaded before all subdomains supported HTTPS was effectively HTTPS-only for months. Treat preload as a deliberate, one-way decision, not a default to flip on.
Defence-in-Depth Checklist
HSTS is one layer. A hardened ASP.NET Core transport setup also includes:
- Force HTTPS with
UseHttpsRedirection()so HTTP requests are upgraded server-side. - Enable HSTS in production with a sensible
max-age, ramped up gradually. - Scope
IncludeSubDomainscarefully - only after verifying every subdomain serves HTTPS. - Treat
Preloadas one-way - submit to the preload list only when you are fully committed. - Keep
UseHstsout of Development to avoid caching HTTPS rules againstlocalhost. - Pair it with the wider header set (content type options, frame options, a content security policy) and a current TLS configuration.
- Verify the header is actually present in production responses, not just assumed.
For a broader pass over your API's security posture, see the ASP.NET Core Security Checklist for 2026, and if you are preparing for senior roles the ASP.NET Core Security Interview Questions cover transport security in depth. Microsoft's own guidance on enforcing HTTPS and HSTS is the authoritative reference for the middleware behaviour.
FAQ
What is HSTS in ASP.NET Core? HSTS (HTTP Strict Transport Security) is a response header, emitted via UseHsts(), that tells browsers to only ever connect to your domain over HTTPS for a set duration. Once a browser has seen it, it refuses plain HTTP connections to that domain automatically, which removes the window SSL stripping relies on.
Does HTTPS redirection make HSTS unnecessary? No. UseHttpsRedirection() upgrades an HTTP request to HTTPS, but that redirect is itself an interceptable HTTP response, and it does nothing for the very first request of a session. HSTS moves enforcement into the browser so no strippable HTTP request is ever sent. They are complementary, not interchangeable.
Why is UseHsts disabled in Development by default? Because HSTS rules are cached by the browser per-domain, and caching an HTTPS-only rule against localhost can break local HTTP tooling and other apps sharing that host. The default template gates UseHsts behind a non-Development check on purpose.
What max-age should I use for HSTS? One year (TimeSpan.FromDays(365)) is the standard production value and is required if you ever want to preload. When first enabling HSTS, start with a short max-age, verify everything still works over HTTPS, then increase it - because the value is cached by browsers and a mistake is slow to walk back.
Is enabling the HSTS preload list safe? It is the strongest form of protection but also the hardest to undo. Preloading requires IncludeSubDomains and a long max-age, and removal only propagates with future browser releases. Only submit a domain once every subdomain reliably serves HTTPS and you are committed to HTTPS-only for the long term.
Does HSTS protect API clients that are not browsers? HSTS is enforced by browsers and other user agents that implement the standard. Non-browser API clients (mobile apps, services, scripts) do not automatically honour the header, so for those you still enforce HTTPS at the server and pin or validate TLS in the client where appropriate.
About the Author
Celin Daniel is Co-founder of Coding Droplets with 13+ years of hands-on experience building and securing .NET and ASP.NET Core APIs in production. The trade-offs above - especially the preload warning - come from real rollouts, not documentation.
- Website: codingdroplets.com
- GitHub: github.com/codingdroplets
- YouTube: youtube.com/@CodingDroplets






