Back to blog

Security headers: HSTS, CSP, X-Frame-Options and others

· 7 min read

In brief: Security HTTP headers are lines the server adds to every HTTP response to tell the browser how to handle your site. Correctly set, they eliminate whole classes of attacks (XSS, clickjacking, MITM) - and they're free.

In brief: Security HTTP headers are lines the server adds to every HTTP response to tell the browser how to handle your site. Correctly set, they eliminate whole classes of attacks (XSS, clickjacking, MITM) - and they're free.

Why they matter

An application can be perfectly backend-side secure, but without proper headers still vulnerable to browser-side attacks: cross-site scripting (XSS), clickjacking, protocol downgrade, MIME confusion. Security headers push defense into the browser.

Strict-Transport-Security (HSTS)

Forces HTTPS for all future visits. After the first HTTPS visit the browser remembers the domain and itself rewrites any http:// link to https://, even if the user clicks a bad answer.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • max-age=63072000 = valid for 2 years
  • includeSubDomains = the rule applies to all subdomains too (caveat: if you have an HTTP-only subdomain, it breaks it)
  • preload = allows inclusion in the HSTS preload list built into Chrome / Firefox / Safari

Caution: HSTS with preload takes 6+ months to remove from the list. Don't include preload before you verify HTTPS works stably for all subdomains.

Content-Security-Policy (CSP)

The most powerful but also hardest header to configure. Whitelists where the browser may load scripts, styles, images, iframes from. Without CSP an attacker who can inject a <script> tag can run arbitrary JS - with CSP only code from approved sources.

Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-...'; img-src 'self' data: https://cdn.example.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'

Common directives:

  • default-src 'self' = by default allow only resources from my domain
  • script-src = JS sources (specify hash or nonce for inline scripts)
  • style-src = CSS sources
  • img-src = images
  • connect-src = AJAX, WebSocket, EventSource
  • frame-ancestors 'none' = nobody may embed the page in an iframe (better than X-Frame-Options)
  • report-uri /csp-report = browser sends JSON for every violation (you catch it in the backend and log)

X-Frame-Options

Older alternative to frame-ancestors. Prevents clickjacking - attacker embeds your page as an iframe and overlays invisible buttons.

X-Frame-Options: DENY

Values: DENY (nobody), SAMEORIGIN (only my domain), ALLOW-FROM uri (deprecated).

X-Content-Type-Options

X-Content-Type-Options: nosniff

Disables MIME sniffing - the browser will respect the Content-Type the server returned. Without it an attacker can upload a file with the wrong type (e.g. an image that's actually HTML with script) and the browser may run it as a web page.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Controls how much information about the previous page the browser sends in the Referer header on click. Default in modern browsers is already strict-origin-when-cross-origin, but the explicit declarative header ensures consistency.

Permissions-Policy

Disables APIs you don't need - camera, microphone, GPS, geolocation. An attacker via XSS cannot request these APIs.

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Practical nginx configuration

server {
  listen 443 ssl http2;
  server_name example.com;

  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "DENY" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

  # CSP is extensive - define according to your application
  add_header Content-Security-Policy "default-src 'self'; script-src 'self'; ..." always;
}

The key is always - without it nginx will omit headers on error responses (4xx, 5xx).

Auditing the current state

Easiest via an online tool. Our header check will show you which headers are missing and which are wrong. For a thorough audit securityheaders.com also gives an A-F score.

Conclusion

Security headers are one of the best investments in effort-to-payoff ratio in security - typically half an hour of setup in reverse proxy against a whole category of browser-side attacks. An audit once every six months as part of regular maintenance is a reasonable minimum.

Free security headers audit

No registration, result in three seconds.

Test the website →


Try ePulz.io free - 7 days, no credit card needed.

Create account