HTTP Strict Transport Security (HSTS) makes browsers treat a hostname as HTTPS-only after a successful secure connection. This blocks downgrade attacks such as SSL stripping and reduces accidental access over plain HTTP once HTTPS is established.

HSTS is enforced when an HTTPS response includes the Strict-Transport-Security header, which browsers cache for the specified max-age. Optional directives like includeSubDomains extend enforcement to subdomains, and the header is honored only when delivered over HTTPS. In Nginx, emitting the header from the TLS server block with the always flag keeps it present on non-2xx responses as well.

A long max-age can create a long-lived lockout if HTTPS breaks, so certificate renewal, redirects, and TLS termination points (reverse proxy or load balancer) must be stable before committing to a year-long policy. Deployments that serve multiple hostnames or multiple server blocks need consistent headers across each HTTPS entry point. Existing add_header directives in nested contexts can override inheritance, so HSTS may need to be declared alongside other custom headers to avoid gaps.

Steps to enable HSTS in Nginx:

  1. Confirm the site responds over HTTPS.
    $ curl -I https://example.com/
    HTTP/2 200
    server: nginx
    ##### snipped #####
  2. Confirm the HTTP endpoint redirects to HTTPS.
    $ curl -I http://example.com/
    HTTP/1.1 301 Moved Permanently
    location: https://example.com/
    ##### snipped #####

    Skip this check if port 80 is intentionally closed for the hostname.

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

    For first rollout, use max-age=300 and increase it after confirming stable HTTPS.

    Repeat the header in each HTTPS server block that serves the hostname (example.com, www.example.com, or any other aliases).

    Site configs are commonly located under /etc/nginx/sites-available (symlinked to /etc/nginx/sites-enabled) or /etc/nginx/conf.d.

    Add includeSubDomains or preload only when every subdomain is HTTPS-ready, since browsers can enforce HTTPS-only access until max-age expires.

  4. Test the Nginx configuration syntax.
    $ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  5. Reload Nginx to apply the change.
    $ sudo systemctl reload nginx
  6. Verify the HSTS header is present on a normal HTTPS response.
    $ curl -I https://example.com/ | grep -i '^strict-transport-security:'
    Strict-Transport-Security: max-age=31536000
  7. Verify the HSTS header is present on an HTTPS error response.
    $ curl -I https://example.com/does-not-exist | grep -i '^strict-transport-security:'
    Strict-Transport-Security: max-age=31536000
Discuss the article:

Comment anonymously. Login not required.