# Strict-Transport-Security (HSTS) Header: Setup and Preload

> Strict-Transport-Security (HSTS) forces HTTPS for a set time. max-age, includeSubDomains, preload rules, nginx and Apache config, and how to roll it back.

Source: https://howhttpworks.com/headers/strict-transport-security
Last reviewed: 2026-10-04

> **TL;DR:** `Strict-Transport-Security: max-age=31536000; includeSubDomains` makes browsers use HTTPS only for your site for a year. Roll it out with a short `max-age` first, because a mistake locks out anyone whose HTTPS breaks, and preloading is very hard to reverse.

## What it does

The first visit to `http://example.com` is vulnerable: an attacker on the network can intercept it and keep the victim on HTTP (SSL stripping). A redirect to HTTPS does not help if the redirect itself is stripped. HSTS closes that gap for later visits:

```http
HTTP/2 200
Strict-Transport-Security: max-age=31536000; includeSubDomains
```

For the next year the browser converts `http://example.com/...` into `https://example.com/...` locally, with no network request, and turns certificate warnings into hard errors with no "proceed" button. In Chrome that is the "Your connection is not private" page with `NET::ERR_CERT_AUTHORITY_INVALID` and no Advanced link; Chrome DevTools shows the local rewrite as `307 Internal Redirect` with `Non-Authoritative-Reason: HSTS`.

Defined in RFC 6797. The header is ignored over plain HTTP, over HTTPS with an invalid certificate, and on IP-address hosts.

## Directives

| Directive | Meaning |
| --- | --- |
| `max-age=<seconds>` | How long the policy lasts, refreshed on every response with the header. `0` clears it |
| `includeSubDomains` | Applies to every subdomain, including internal ones such as `intranet.example.com` |
| `preload` | Signals consent to be on the browser-shipped preload list. The header does not add you; submission does |

## Safe rollout

1. Make sure every URL you serve works on HTTPS, including subdomains if you use `includeSubDomains`, and that HTTP redirects with 301 or 308 to HTTPS.
2. Send `max-age=300` and watch for problems.
3. Step up: `86400`, `604800`, then `31536000`.
4. Add `includeSubDomains` only after every subdomain, including old staging and mail hosts, is on valid HTTPS.
5. Consider preload last.

## Preload requirements

From hstspreload.org:

- A valid certificate and redirect from HTTP to HTTPS on the same host.
- All subdomains served over HTTPS.
- The header on the apex domain's HTTPS response with `max-age` of at least `31536000` (one year), `includeSubDomains`, and `preload`.

```http
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```

Submit at hstspreload.org. Removal is possible but it ships with browser releases, so users may be locked to HTTPS for months. Do not preload if any subdomain cannot be HTTPS.

## Configuration

**nginx.** Use `always` so error responses carry it too. `add_header` in a `location` discards inherited `add_header` lines, so put it at `server` level or repeat it.

```nginx
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
```

**Apache:**

```apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
```

**Express (helmet).** Helmet's default is `max-age=31536000; includeSubDomains`:

```javascript
const helmet = require('helmet')
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: false }))
```

**Django:**

```python
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = False
SECURE_SSL_REDIRECT = True
```

**Behind a TLS-terminating proxy or Cloudflare.** Set it on the layer that speaks HTTPS to clients. Cloudflare has an HSTS toggle under SSL/TLS, Edge Certificates. Do not also set it at the origin with different values.

## Verify

```bash
curl -sI https://example.com | grep -i strict-transport-security
```

```text
strict-transport-security: max-age=31536000; includeSubDomains
```

In Chrome, `chrome://net-internals/#hsts` lets you query a domain and delete its policy, which is the fast way to recover your own browser after a bad rollout. Firefox users clear it with "Forget about this site".

## Gotchas

- The first visit is still unprotected unless the domain is preloaded; HSTS is "trust on first use".
- `localhost` and raw IPs are not affected, but a custom dev domain with HSTS and `includeSubDomains` can break local `http://dev.example.com` setups.
- A max-age of 0 over HTTPS is the only way to cancel; letting the cert expire locks users out instead.
- Cookies are not made secure by HSTS; still set the [Secure](https://howhttpworks.com/cookies/secure) flag.

## Related

- [HTTPS and TLS guide](https://howhttpworks.com/guides/https-and-tls), [Set-Cookie](https://howhttpworks.com/headers/set-cookie), [Content-Security-Policy](https://howhttpworks.com/headers/content-security-policy) (`upgrade-insecure-requests`)
- [Site verifier tool](https://howhttpworks.com/tools/site-verifier)

See also the comparison [HTTP vs HTTPS](https://howhttpworks.com/compare/http-vs-https): what TLS adds, what it still leaves visible, and HSTS.
