HSTS is a header that tells browsers never to connect to your domain over plain HTTP again. It prevents a whole class of interception attack and it has one property that makes it unlike other headers: the browser remembers it, and you cannot take it back.
What it does
Strict-Transport-Security: max-age=31536000
A browser receiving this will, for the next year, refuse to make a plain connection to the domain. It upgrades the request itself, before contacting anything.
That closes the gap where a visitor typing the address is briefly on an unencrypted connection before your redirect fires, which is precisely the moment an interception attack works.
A redirect alone does not close it, which is why the header is worth having on top of one. Forcing HTTPS goes over the redirect.
Why it is dangerous
Removing the header does nothing to browsers that already stored it. They keep refusing plain connections for the full period you originally stated.
So if the certificate later expires, or the site moves somewhere without one, those visitors do not see a warning they can click through. They cannot reach the site at all. And there is no server-side way to release them.
The only exit is fixing the certificate.
Start with a very short period
Header always set Strict-Transport-Security "max-age=300"
Five minutes. If something is wrong, it expires almost immediately.
Confirm every part of the site works over HTTPS: deep pages, the login, forms, images, anything embedded. Then raise it in stages: a day, a week, a year.
Reaching a year gradually costs a fortnight and removes the possibility of a mistake that lasts twelve months.
includeSubDomains is the part that bites
Strict-Transport-Security: max-age=31536000; includeSubDomains
This extends the rule to every name under the domain: including ones you have forgotten, ones without certificates, and ones created later.
The classic failure: an internal tool at tools.example.com, running happily on plain HTTP, becomes unreachable for everyone in the company the moment the main site sends this header.
Before adding it, list every subdomain that exists and confirm each has a working certificate. There is more on finding the ones that get missed in SSL for subdomains and addon domains.
Preloading is close to permanent
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Adding preload and submitting the domain puts it on a list compiled into browsers themselves. Visitors then refuse plain HTTP before ever contacting your server, including on their first visit.
That is the strongest form of the protection, and removal is a request that takes months to reach users through browser releases.
Only do this if HTTPS for the domain and all its subdomains is a permanent commitment. For most sites the year-long header without preloading is the right amount of protection with a recoverable failure mode.
Where to set it
<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=300" </IfModule>
Set it only on the HTTPS response. A browser ignores the header on a plain connection anyway, and sending it there is a sign the configuration is not doing what its author thought.
Confirm it is actually arriving:
curl -sI https://example.com | grep -i strict
Setting up website security headers deals with the others that belong alongside it.
Before enabling it at all
Make sure certificate renewal is reliable, because HSTS converts an expired certificate from an inconvenience into an outage.
That means automatic renewal that is working, and an alert if it fails, renewing your certificate and checking a certificate from the command line cover both halves.
Confirm what is actually being sent
The policy only exists if the header reaches the browser, and it is easy to configure it where it never does.
curl -sI https://example.com/ | grep -i strict-transport curl -sI https://example.com/some/page | grep -i strict-transport curl -sI http://example.com/ | grep -i strict-transport
Two things to read. It must appear on every response, not only the home page, because a visitor whose first request is a deep page gets whatever that page sends.
And it must not appear on the plain HTTP response. Browsers ignore it there, and its presence usually means the header is being added by something that does not know which protocol it is answering, which is worth fixing before extending the period.
Removing it is slower than adding it
The instruction lives in the browser, not on your server, so deleting the header does nothing to visitors who already have it.
To withdraw it you have to keep serving HTTPS, with a valid certificate, and change the header to a zero lifetime. Every returning visitor then clears it on their next visit, and only after they visit.
Strict-Transport-Security: max-age=0
This is why the certificate has to stay valid throughout. A site that loses its certificate while a long policy is active is not reachable at all, and there is nothing you can serve to fix it, because the browser refuses to connect in the first place. Renewing your SSL certificate deals with keeping that from happening.
The subdomains you have forgotten
Including subdomains is the setting that causes outages, because the list of names under a domain is longer than anyone remembers.
Enumerate before enabling it, not after. Staging sites, internal tools, a status page, a mail hostname, a monitoring dashboard, anything on a device with a self-signed certificate: each becomes unreachable if it is not serving valid HTTPS.
dig example.com AXFR @ns1.example.com 2>/dev/null | awk '{print $1}' | sort -u
awk '{print $1}' /etc/hosts | sort -u
Where zone transfer is refused, the zone file in your DNS panel is the list. Go through it by hand once. It is an hour of work against an outage that affects things nobody is monitoring.
Your own browser will lie to you
Testing is unreliable because the browser you are testing with has already recorded the policy.
Use a fresh profile or a private window for each test, and clear the stored entry between attempts. Chromium based browsers expose this at chrome://net-internals/#hsts, where a domain can be queried and deleted individually.
The same applies to the preload list. Being added takes weeks and being removed takes months, during which every browser that shipped with your domain on the list will refuse plain HTTP regardless of what you serve. Treat preloading as a decision that is difficult to reverse rather than a stronger version of the same setting.