# How to enable HSTS safely (Nginx, Apache, Caddy, IIS)

> Add the Strict-Transport-Security header step by step: short max-age first, then includeSubDomains and preload, without locking users out.

Source: https://ssl.sh/guides/enable-hsts/
Last updated: 2026-11-10

Enable HSTS by sending a `Strict-Transport-Security` header on HTTPS responses, starting with `max-age=300` and raising it in stages to one or two years. Browsers then refuse to load the site over plain HTTP. Add `includeSubDomains` and `preload` only after every subdomain serves HTTPS.

The examples cover Nginx, Apache httpd 2.4, Caddy 2 and IIS 10.

## Prerequisites

HSTS removes the option to fall back to HTTP, so HTTPS must work everywhere first.

- A valid certificate with automatic renewal (see [automatic renewal](https://ssl.sh/guides/renew-certificates-automatically/)).
- An HTTP-to-HTTPS redirect in place ([redirect HTTP to HTTPS](https://ssl.sh/guides/redirect-http-to-https/)).
- No mixed content and, if you plan `includeSubDomains`, HTTPS on every subdomain.

## Step 1: Add the header with a short max-age

Start with `max-age=300`, so a mistake locks browsers to HTTPS for five minutes only. **Nginx**, in the `listen 443 ssl` server block:

```nginx
add_header Strict-Transport-Security "max-age=300" always;
```

`add_header` directives are inherited only if a `location` block defines none of its own; repeat the line in any location that sets other headers.

**Apache**, inside the `*:443` virtual host (needs `a2enmod headers`):

```apache
Header always set Strict-Transport-Security "max-age=300"
```

**Caddy**, inside the site block:

```caddy
header Strict-Transport-Security "max-age=300"
```

**IIS**, in the site's `web.config`:

```xml
<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Strict-Transport-Security" value="max-age=300" />
    </customHeaders>
  </httpProtocol>
</system.webServer>
```

Reload the server after the change.

## Step 2: Raise max-age in stages

Raise max-age one level at a time, and watch for problems for a few days at each level:

```text
max-age=300          (5 minutes)
max-age=86400        (1 day)
max-age=604800       (1 week)
max-age=31536000     (1 year)
max-age=63072000     (2 years)
```

## Step 3: Add includeSubDomains

`includeSubDomains` extends the policy to every subdomain, so add it only after each one serves HTTPS:

```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
```

## Step 4 (optional): Preload

Preloading builds the policy into browsers. To qualify, the apex domain must serve `max-age=31536000` or more with `includeSubDomains; preload`, have a valid certificate, and redirect HTTP to HTTPS on the same host before redirecting elsewhere:

```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
```

Then submit the domain at hstspreload.org. Read the removal section there first.

## Verify

The live site should return the header with the max-age you set. Check it:

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

The [HSTS checker](https://ssl.sh/tools/hsts-checker/) also reports preload eligibility. Confirm the certificate behind it with the [SSL checker](https://ssl.sh/tools/ssl-checker/), or from any terminal:

```bash
curl ssl.sh/example.com
```

## Automatic renewal

HSTS turns certificate problems into outages: when the certificate expires or does not match, browsers show an error with no "proceed anyway" option. That makes reliable renewal mandatory. Use an ACME client that runs at least daily and follows ARI, reload services after renewal, and monitor the live certificate with [ssl.sh monitoring](https://ssl.sh/monitor/) (in development). This matters more as lifetimes shrink: 200 days maximum since March 15, 2026, Let's Encrypt's default 64 days from February 10, 2027, and 47 days maximum from March 15, 2029.

## Troubleshooting

- **Header missing on some pages**: an Nginx `location` with its own `add_header` drops inherited headers, or `always` is missing so error pages lack it.
- **Users cannot bypass a certificate error**: that is HSTS working as designed; fix the certificate, see [HSTS cannot bypass](https://ssl.sh/errors/hsts-cannot-bypass/) and [NET::ERR_CERT_DATE_INVALID](https://ssl.sh/errors/net-err-cert-date-invalid/).
- **An internal subdomain broke after includeSubDomains**: give it a valid certificate, or lower max-age to 0 on the apex and wait for browsers to forget.
- **Header sent over HTTP**: harmless but ignored; browsers only accept HSTS over HTTPS.
- **Preload submission rejected**: the checker on hstspreload.org lists the failing requirement, often a missing redirect on the apex or a max-age below one year.
