toolhq.io

All posts
7 min readby Jameel Haider

Security headers: a practical checklist and rollout order

Security headers are instructions your server sends to the browser along with each response. They cost nothing, change nothing about how a page looks, and limit the damage when something else goes wrong: a clickjacking attempt, a script injected into a page, a downgrade to plain HTTP. Most sites set few or none of them.

There are plenty of guides to what each header means, including HTTP headers explained, content security policy explained and HSTS and preload explained. This post is about the other question: what to do first, in what order, and how to avoid breaking your own site.

The checklist

The Security Headers Scanner grades eight headers out of 100. Here is each one with its weight in that score, which also doubles as a rough priority order.

HeaderWeightWhat it does
Strict-Transport-Security25Tells browsers to use HTTPS only
Content-Security-Policy25Limits what the page may load and run
Framing protection10Stops other sites embedding yours in a frame
X-Content-Type-Options10Stops the browser guessing a file's type
Referrer-Policy10Controls how much of the URL leaves the site
Permissions-Policy10Restricts camera, location and similar features
Cross-Origin-Opener-Policy5Isolates the page from windows it opens
Cross-Origin-Resource-Policy5Limits which sites may load your resources

The weights are a judgement, not a standard, and the grade is a prompt to review. It is not an audit.

Rollout order

The headers differ in risk. Some can be added in a minute with essentially no chance of breaking anything. Others can take a site down if rushed. Do the safe ones first.

1. The safe three

These almost never break anything.

add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;

nosniff stops browsers from treating a file as a different type than the one you declared. strict-origin-when-cross-origin sends the full URL to your own pages and only the origin to others, which is a sensible default. SAMEORIGIN allows your own pages to frame each other and blocks everyone else. If you need to be embedded elsewhere, such as a widget, use a CSP frame-ancestors list instead. The scanner accepts either one as framing protection.

The always keyword matters in nginx. Without it, the header is only added on successful responses, so error pages go out unprotected.

2. HSTS, in stages

Strict-Transport-Security is powerful and hard to undo, because browsers remember it for the whole max-age. If you set a long value and then a subdomain turns out not to support HTTPS, you have locked visitors out of it.

Raise it in steps:

# Week 1
add_header Strict-Transport-Security "max-age=300" always;
# Once nothing broke
add_header Strict-Transport-Security "max-age=86400" always;
# Then
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;

Add includeSubDomains only when every subdomain, including ones you have forgotten, serves HTTPS. Add preload only after reading what preloading commits you to. The scanner gives full credit from 180 days and flags anything shorter as short. HSTS only takes effect on a response served over HTTPS, so check that your site also redirects HTTP to HTTPS first, which the redirect tracer will show.

3. Permissions-Policy

add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Disable the features your site does not use. It is low risk when you know the list, and a short allow list is better than none. Review it when you add features such as embedded maps or video calls.

4. Content Security Policy, last and carefully

CSP is the most valuable header and the one most likely to break a page, because it blocks anything not on your list, including scripts you did not know you loaded.

Start in report only mode, which logs violations without blocking anything:

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-reports" always;

This needs an endpoint at that path that accepts the reports, otherwise nothing is collected. report-uri is the older directive and is widely supported. The newer report-to also needs a Reporting-Endpoints header, so check your setup actually delivers reports before relying on it. Read the reports for a while, add the sources that are legitimate, and only then switch to enforcing. Avoid unsafe-inline and unsafe-eval if you can, because they allow exactly the injected code a CSP exists to stop. If you rely on inline scripts, move them to files, or use nonces or hashes. The scanner marks a policy containing those as a warning with partial credit, not a failure, because moving away from them takes real work.

5. The cross origin pair

Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy are optional hardening and earn the fewest points. They can break pop up logins and cross site embeds, so test before enabling. same-origin is the strictest setting and same-site is gentler.

Mistakes that bite

Setting headers in the wrong place. In nginx, add_header in a location block replaces the ones inherited from the server block rather than adding to them. If a header disappears for one path, this is usually why.

Forgetting always. As above, error responses miss the headers.

Setting a header in a meta tag. Only some policies work that way and HSTS does not. Send them as real HTTP headers.

Duplicating headers. If your application and your proxy both add the same header, the browser may see two values and behave unpredictably. Set each in one layer.

Turning on includeSubDomains early. The failure shows up weeks later when a forgotten subdomain breaks.

Reading a grade

Even well run sites get middling grades, because the grader looks at values as well as presence. A large, well known site can score around a C, with a long CSP that contains unsafe-inline, no Permissions-Policy, and neither cross origin header. That is not a sign that anyone is careless. It reflects real constraints, and it is a reminder to read the line by line explanation instead of chasing a letter.

A bare site with none of the headers scores near the bottom, as the documentation domain example.com did when fetched over plain HTTP on 4 November 2026: none of the eight were present. That is the starting point for most sites, and the safe three plus HSTS alone move it a long way.

A routine

Scan your site with the security headers scanner to see where you are. Add the safe three. Roll out HSTS in stages. Add Permissions-Policy. Introduce CSP in report only mode and tighten it over time. Scan again after each change, and check cookies too, because cookie flags are the other half of this. If a header you set is missing from the scan, the HTTP header checker shows the raw response.