HTTP Strict Transport Security (HSTS) tells browsers to stop retrying a site over plain HTTP after a successful secure visit. Once a browser learns that policy, it upgrades future requests to HTTPS automatically and blocks certificate bypass clicks that would reopen downgrade or SSL-stripping risk.

In Nginx, HSTS is sent with the Strict-Transport-Security response header from the HTTPS virtual host. The max-age value controls how long the browser keeps the policy, and the always parameter on add_header keeps the header on error responses as well as successful ones so the policy does not disappear on 404 or 500 pages.

Enable HSTS only after the site already has a trusted certificate and a stable HTTP-to-HTTPS redirect path, because a broken TLS configuration can lock users out until the cached policy expires. If a nested location or if in location block defines its own add_header directives, verify that the HSTS header still appears there as well instead of assuming the server-level header will be inherited automatically.

Steps to enable HSTS in Nginx:

  1. Confirm the site already answers over HTTPS with the expected certificate and hostname.
    $ curl -sSI https://example.com/
    HTTP/2 200
    server: nginx/1.28.3
    date: Thu, 09 Apr 2026 13:37:38 GMT
    content-type: text/html
    content-length: 10671
    last-modified: Wed, 08 Apr 2026 04:12:52 GMT

    Do not enable HSTS on a hostname that still shows certificate warnings, mismatched names, or an incomplete certificate chain.

  2. Confirm that plain HTTP requests already redirect to the secure URL.
    $ curl -sSI http://example.com/
    HTTP/1.1 301 Moved Permanently
    Server: nginx/1.28.3
    Date: Thu, 09 Apr 2026 13:37:38 GMT
    Content-Type: text/html
    Content-Length: 169
    Connection: keep-alive
    Location: https://example.com/

    Skip this check only when port 80 is intentionally closed for the hostname and readers are not expected to begin on HTTP.

  3. Open the Nginx site configuration file that serves the HTTPS hostname.
    $ sudoedit /etc/nginx/sites-available/example.com

    Site files are commonly stored under /etc/nginx/sites-available with symlinks in /etc/nginx/sites-enabled, or directly under /etc/nginx/conf.d.

  4. Add the Strict-Transport-Security header inside the HTTPS server block.
    server {
        listen 443 ssl;
        server_name example.com www.example.com;
    
        add_header Strict-Transport-Security "max-age=31536000" always;
    
        ##### snipped #####
    }

    For a first rollout, start with a short value such as max-age=300, verify behavior, then increase it to your intended long-lived policy.

    Repeat the header in every HTTPS server block that serves the hostname or alias, such as example.com and www.example.com.

    Add includeSubDomains or preload only when every current and future subdomain is ready for HTTPS, because browsers can keep enforcing that policy until max-age expires.

    To remove an already deployed HSTS policy, serve Strict-Transport-Security: max-age=0 over HTTPS. Simply deleting the header does not clear a browser policy that is still cached.

  5. If the configuration already sets custom headers in a nested location or if in location block, verify that the HSTS header is still emitted there as well.

    Under the standard Nginx inheritance model, a lower level that defines its own add_header directives can change which headers are sent for that request path.

  6. Test the Nginx configuration syntax before reloading the service.
    $ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  7. Reload Nginx to apply the validated header change.
    $ sudo systemctl reload nginx

    Use sudo nginx -s reload on systems where systemd is not managing the service.

  8. Confirm the service stayed active after the reload.
    $ sudo systemctl is-active nginx
    active

    If the unit reports failed or inactive, inspect the service log or journal before retrying the reload.

  9. Verify that the HSTS header appears on a normal HTTPS response.
    $ curl -sSI https://example.com/ | grep -i '^strict-transport-security:'
    Strict-Transport-Security: max-age=31536000

    The header name is case-insensitive, but using the canonical Strict-Transport-Security spelling makes configuration reviews easier.

  10. Verify that the HSTS header also appears on an HTTPS error response.
    $ curl -sSI https://example.com/does-not-exist | grep -i '^strict-transport-security:'
    Strict-Transport-Security: max-age=31536000

    If this check returns nothing, the header is usually missing the always parameter or being overridden in a more specific block.