HSTS
HTTP Strict Transport Security. A header a site sends over HTTPS to order a conforming browser to use only HTTPS for that host for a set time. It blocks protocol downgrade and redirect tampering on later visits; it encrypts nothing itself and cannot protect a first visit unless preloaded.
Also known as HTTP Strict Transport Security.
Full Explanation
HSTS (HTTP Strict Transport Security) is a policy. A site declares it in an HTTPS response header called Strict-Transport-Security. The policy tells a conforming browser to reach that host only over HTTPS for a stated number of seconds. It also tells the browser to rewrite any http:// URL for the host to https:// before the request ever leaves the machine. HSTS is defined in RFC 6797, which puts it in one sentence: "An HSTS Policy directs UAs to communicate with a Known HSTS Host only over secure transport and specifies policy retention time duration" (section 5.2).
Three things it is not. It is not encryption. TLS does that. The specification simply presumes "TLS or SSL as the underlying secure transport" (section 14.1). It is not a defence against phishing or malware. The RFC states outright: "it is explicitly not a remedy for two other classes of threats: phishing and malware" (section 2.3). And it is not protection for a first visit. A browser can only learn the policy from a response it has already received. This leaves the first plain-HTTP request to an unknown host exposed (section 14.6). Browser preload lists exist to close that gap.
For CDN work the entry reduces to three points. The browser enforces the policy against the response it actually received. For a CDN-fronted hostname, that response comes from the edge. So the header has to be on edge responses, including the ones the origin never sees. The policy itself is only two directives: max-age and includeSubDomains. Everything else, preload included, is browser-vendor machinery outside the RFC. And switching it on is an availability commitment. For the whole max-age, that hostname must keep serving valid HTTPS. That is because a browser holding the policy cannot fall back to HTTP, and it is not offered a way to click through a certificate error.
How it works
A host declares the policy by "issuing to UAs an HSTS Policy, which is represented by and conveyed via the Strict-Transport-Security HTTP response header field over secure transport (e.g., TLS)" (section 5.1). RFC 6797 defines exactly two directives (section 6.1):
- max-age (required): "the number of seconds, after the reception of the STS header field, during which the UA regards the host (from whom the message was received) as a Known HSTS Host" (section 6.1.1). The RFC describes it as a time-to-live value, "relative to the reception time of the STS header field" (section 8.1). So every fresh header restarts the clock. max-age=0 tells the browser to drop the policy (section 6.1.1).
- includeSubDomains (optional): a valueless directive. It signals that the policy "applies to this HSTS Host as well as any subdomains of the host's domain name" (section 6.1.2).
Establishing the policy is deliberately narrow. A browser "MUST ignore any present STS header field(s)" on a response received over insecure transport. Errors or warnings in the secure transport also stop it from noting or updating the policy (section 8.1). A server "MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport" (section 7.1). If a response carries more than one such field, "the UA MUST process only the first such header field" (section 8.1).
While the host is a Known HSTS Host, every http:// load for it is rewritten before the request goes out. The scheme becomes https. An explicit port 80 becomes 443. Any other explicit port is preserved, and no port is added if the URL had none. The RFC's note is that "these steps ensure that the HSTS Policy applies to HTTP over any TCP port of an HSTS Host" (section 8.3). A host written as an IP literal never matches a Known HSTS Host (section 8.3). So the policy cannot attach to a bare address. On any failure of the secure transport the browser MUST terminate the connection, whether the error is at warning level, fatal level or any other level (section 8.4). The RFC's implementation advice is that this be done with "no user recourse", meaning "the user should not be presented with a dialog giving her the option to proceed" (section 12.1). That is the behaviour users meet in practice. An HSTS host with an expired or mismatched certificate is unreachable, not warned about.
includeSubDomains is about more than coverage breadth. The RFC's stated reason for needing it is domain cookies. A Secure-flagged cookie set for example.com is still sent over plain HTTP to a subdomain the browser does not know as an HSTS host. If an attacker can register a name in that domain and answer on it, the cookie leaks (section 14.4).
The specification gives its own examples. One year for the issuing domain only: "Strict-Transport-Security: max-age=31536000". Roughly six months including subdomains: "Strict-Transport-Security: max-age=15768000 ; includeSubDomains" (section 6.2). The one-year-plus-includeSubDomains form common in production comes from the preload list rather than the RFC. hstspreload.org requires that "the max-age must be at least 31536000 seconds (1 year)", together with includeSubDomains and preload, served on the base domain (the zone apex).
That preload directive is the one piece not in RFC 6797. As Fastly docs put it, "although the token is not part of the HSTS specification, including it in the header is a prerequisite for submitting to this preloaded list." The list is the facility the RFC anticipated. Browsers are "pre-configured with HSTS Policy for their site(s) by the UA vendor(s)", shipped the way root CA certificates are. This "would help protect against the bootstrap MITM vulnerability" (section 12.3). Chrome builds the list into the browser, and Firefox, Safari, IE 11 and Edge maintain lists based on it. Sending the directive counts as asking to be included. Note the current position of the people who run it, though: "While HSTS is recommended, HSTS preloading is not recommended". That is because Chrome and Safari now upgrade HTTP navigations to HTTPS regardless of any policy, so preloading "only provides value when these upgrades fail in the presence of an active attacker" (hstspreload.org; Chrome's automatic upgrades are described on the Chromium blog).
Why it matters for a CDN
The browser judges the response it received. For a CDN-fronted hostname, the edge server terminates TLS and produces that response. So the operative question is not whether the origin sends HSTS but whether the header appears on the edge response. Either route works: the CDN can add the header itself, or pass the origin's through. Each of CloudFront's security-header settings carries an origin-override flag: "when this setting is set to false and the origin response contains the header, CloudFront includes the header that it received from the origin in the response that it sends to the viewer" (CloudFront docs). Cloudflare lists adding the header at the origin, or a response header transform rule, as alternatives to its own HSTS switch (Cloudflare docs). What you verify is the edge response.
The responses that catch people out are the ones the origin never sees: HTTPS-to-HTTPS redirects issued at the edge, edge error and challenge pages, and objects already sitting in cache. hstspreload.org calls out the redirect case specifically: "if you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to)".
Duplication is the other edge-specific failure. If the origin already sends the header and the CDN adds its own, the browser processes only the first field it sees (section 8.1). A short origin max-age can silently shadow the long policy configured at the edge. Pick one emitter and strip the other.
What the policy buys is protection for exactly the step a CDN deployment depends on. The edge answers port 80 with a permanent redirect to HTTPS. As hstspreload.org describes the attack, "if a site redirects from HTTP to HTTPS, an on-path network attacker can intercept and re-write the redirect to keep the browser using plaintext HTTP." A browser holding the policy never issues that HTTP request at all.
The policy binds the browser to the edge and says nothing about the hop behind it. Traffic from the edge to the origin is governed by your origin settings: TLS to origin, certificate verification, mTLS. None of that is governed by a header the browser stored.
Finally, enabling HSTS at a CDN commits the hostname operationally. Cloudflare's documentation tells you to avoid, once HSTS is on, changing DNS records from proxied to DNS only, pausing Cloudflare, pointing nameservers away, redirecting HTTPS to HTTP, or letting certificates go invalid or mismatched. It warns that if you remove HTTPS before the policy expires "your website becomes inaccessible to visitors for the duration of the Max Age Header or until you enable HTTPS" (Cloudflare docs). Migrations, failover paths and certificate renewals all inherit that constraint.
What CDNs do
- Cloudflare: opt-in from the Edge Certificates page. When enabled it "serves HSTS headers to browsers for all HTTPS requests. HTTP (non-secure) requests will not contain the header." The Max Age Header setting is required and offers "Disable, or a range from 1 to 12 months". Applying the policy to subdomains, Preload and a No-Sniff header are separate options. The feature is documented as available on Free, Pro, Business and Enterprise. Cloudflare also notes that a minimum Max Age Header of 12 months is required for preload-list inclusion (Cloudflare docs).
- Fastly: "Fastly provides a setting that allows you to force TLS and enable HSTS at the same time", the Force TLS and enable HSTS switch under the service's Settings. This creates the redirect request setting and the header for you. Manually, you set a response header on http.Strict-Transport-Security with your own max-age plus optional
includeSubdomainsandpreloadtokens. You disable HSTS by setting "the max-age to 0 on an HTTPS connection" (Fastly docs). - AWS CloudFront: CloudFront adds the header when you attach a response headers policy whose Strict-Transport-Security settings specify a max-age (required) plus optional includeSubDomains and preload booleans (CloudFront docs). The required Override boolean "determines whether CloudFront overrides the Strict-Transport-Security HTTP response header received from the origin with the one specified in this response headers policy" (CloudFormation reference). With Override false, an origin that sends the header keeps control of it.
- Akamai: an HTTP Strict Transport Security (HSTS) behavior you insert into a Property Manager rule, whose Enable field "turns the HTTP Strict Transport Security (HSTS) behavior on or off". Max age is a fixed list rather than a free number: 0 minutes (disable), 10 minutes, 1 day, 1 month, 3 months, 6 months, 1 year and 2 years, with 2 years marked "the recommended setting". Alongside it sit Include all subdomains, Preload, and Redirect all HTTP requests to HTTPS with a 301 or 302. Akamai documents that "changing the value of Max age affects new connections only". Akamai also documents that HSTS cannot share a request with its HTTP to HTTPS Upgrade, HTTPS Cache Key Sharing or Protocol Downgrade behaviors (Akamai docs).
Watch out for
includeSubDomainsreaches every subdomain, internal and intranet hosts included: "if any such subdomains do not support properly configured secure transport, then they will be rendered unreachable from such UAs" (section 14.5). Fastly adds the certificate half of the problem. You need a wildcard or a certificate that covers every subdomain before you assert it (Fastly docs).- Preload is close to irreversible. "Be aware that inclusion in the preload list cannot easily be undone. Domains can be removed, but it takes months for a change to reach users with a Chrome update and we cannot make guarantees about other browsers" (hstspreload.org). Getting in is just as slow. The list ships with browser builds, and Akamai warns it "can take months for your domain to be included in the list and propagated to large numbers of users" (Akamai docs).
- Never ship the
preloaddirective by default. The list's maintainers: "We get regular emails from site operators who tried out HSTS this way, only to find themselves on the preload list without realizing that some subdomains cannot support HTTPS" (hstspreload.org). - The first visit stays unprotected (section 14.6). The policy is only as good as the client's clock. Active attacks on network time protocols make "HSTS less effective against clients that trust NTP or lack a real time clock" (section 14.7).
- "Non-conformant user agents ignore the Strict-Transport-Security header field" (section 14.2). So the port-80 redirect has to stay. An HSTS host that receives a non-secure request "SHOULD send an HTTP response message containing a status code indicating a permanent redirect, such as status code 301" (section 7.1).
- The header is meaningless on plain HTTP. Browsers ignore it and servers must not send it (section 8.1, section 7.1). Adding it to port-80 responses is a misconfiguration, not extra safety.
- max-age is a rolling window, not a fixed deadline: it counts from each reception (section 8.1). Shortening it, or setting it to zero, only reaches browsers that come back and pick up a fresh header before the old one expires.
Best practice
- Emit the header on every HTTPS response the edge returns, redirects and error pages included. Check it against the edge rather than the origin.
- Ramp max-age up. hstspreload.org gives the stages as 5 minutes (max-age=300), 1 week (max-age=604800) and 1 month (max-age=2592000), each with
includeSubDomains. At every stage, "check for broken pages and monitor your site's metrics" and wait out the full max-age before moving on. A year is the destination, not the starting value. - Assert
includeSubDomainsonly once every subdomain, including www, internal, delegated and staging, is confirmed on HTTPS with a certificate that covers it (section 14.5). - Leave
preloadoff unless you have a specific reason for it, because the list's maintainers no longer recommend it (hstspreload.org). If you do submit, meet every requirement: a valid certificate, an HTTP-to-HTTPS redirect on the same host, all subdomains on HTTPS including www, max-age of at least 31536000,includeSubDomainsandpreload. Keep meeting them too, since sites "may be removed automatically in the future for failing to keep up the requirements" (hstspreload.org). - Keep the HTTP-to-HTTPS redirect in place permanently for clients that ignore the header. Never treat HSTS as the only thing forcing HTTPS (section 14.2).
- Retire in the right order: send max-age=0 over HTTPS (section 6.1.1). Then keep HTTPS working until the longest max-age you ever issued has expired for real users. Removing HTTPS first locks out everyone still holding the policy (Cloudflare docs).
- Treat certificate and DNS operations on HSTS hostnames as availability-critical: no proxy bypass, no HTTPS-to-HTTP redirect, no gap in certificate coverage for the life of the policy.
Examples
Set HSTS in Nginx:
server {
listen 443 ssl;
server_name cdn.example.com;
# HSTS: 1 year, include subdomains, preload-ready
add_header Strict-Transport-Security
"max-age=31536000; includeSubDomains; preload"
always;
}
# Redirect HTTP to HTTPS (needed before HSTS kicks in)
server {
listen 80;
server_name cdn.example.com;
return 301 https://$host$request_uri;
}
Check the HSTS status:
# Verify HSTS header
curl -sI https://cdn.example.com/ | grep -i strict
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# Check preload status
# Visit: https://hstspreload.org/?domain=example.com
Frequently Asked Questions
HTTP Strict Transport Security. A header a site sends over HTTPS to order a conforming browser to use only HTTPS for that host for a set time. It blocks protocol downgrade and redirect tampering on later visits; it encrypts nothing itself and cannot protect a first visit unless preloaded.
Set HSTS in Nginx:
server {
listen 443 ssl;
server_name cdn.example.com;
# HSTS: 1 year, include subdomains, preload-ready
add_header Strict-Transport-Security
"max-age=31536000; includeSubDomains; preload"
always;
}
# Redirect HTTP to HTTPS (needed before HSTS kicks in)
server {
listen 80;
server_name cdn.example.com;
return 301 https://$host$request_uri;
}
Check the HSTS status:
# Verify HSTS header
curl -sI https://cdn.example.com/ | grep -i strict
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# Check preload status
# Visit: https://hstspreload.org/?domain=example.com
Yes. HSTS is also known as HTTP Strict Transport Security. HTTP Strict Transport Security. A header a site sends over HTTPS to order a conforming browser to use only HTTPS for that host for a set time. It blocks protocol downgrade and redirect tampering on later visits; it encrypts nothing itself and cannot protect a first visit unless preloaded.