How to Secure HTTP Response Headers: A Practical Checklist
The headers worth setting, safe starting values, how to configure them on Apache and Nginx, and which old ones to leave behind.
Response headers are instructions your server sends along with every page. A handful of them switch on protections that browsers already know how to enforce, which makes them one of the cheapest security improvements a site can make. They are not a substitute for secure code, but they limit the damage when something else goes wrong.
The checklist
| Header | Safe starting value | Purpose |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Always use HTTPS |
| X-Content-Type-Options | nosniff | Stop MIME type guessing |
| Referrer-Policy | strict-origin-when-cross-origin | Limit what is leaked in the Referer header |
| Content-Security-Policy | Tailored to your site | Restrict where resources can load from |
| frame-ancestors (in CSP) or X-Frame-Options | frame-ancestors 'none' or DENY / SAMEORIGIN | Prevent clickjacking |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | Disable features you do not use |
Strict-Transport-Security
HSTS tells the browser to use HTTPS only for the given number of seconds. Start with a short max-age while you confirm that everything works over HTTPS, then raise it to a year. includeSubDomains extends the rule to every subdomain, so confirm they all support HTTPS first. Preload is a separate, difficult-to-reverse commitment; read the dedicated HSTS article before opting in.
X-Content-Type-Options
nosniff makes browsers trust the declared Content-Type instead of guessing. It stops a class of attack in which an uploaded file that looks like script is executed as script. It has no downside if your content types are correct.
Referrer-Policy
When a visitor follows a link, the browser may send the previous page address in the Referer header. strict-origin-when-cross-origin sends the full URL to your own pages, only the origin to other sites, and nothing when moving from HTTPS to HTTP. It keeps paths and query strings, which may hold tokens or private identifiers, out of other sites' logs.
Clickjacking protection
Attackers can load your page in a hidden frame and trick visitors into clicking it. The modern control is the frame-ancestors directive of Content-Security-Policy. X-Frame-Options, with DENY or SAMEORIGIN, is the older equivalent and is still worth sending for older browsers. If both are present browsers that support frame-ancestors use it.
Content-Security-Policy
CSP is the most powerful and the hardest to get right. A good policy lists where scripts, styles, images and connections may come from, and denies the rest. Build it incrementally in report-only mode, and avoid 'unsafe-inline' where you can. The CSP Analyzer and the guide to fixing CSP errors cover this in depth.
Permissions-Policy
This header turns browser features on or off for your page and for frames it embeds. If your site never uses the camera, microphone or geolocation, disable them so injected or embedded code cannot ask.
Cookies
Not a header on its own, but set in Set-Cookie: add Secure so the cookie is only sent over HTTPS, HttpOnly so scripts cannot read it, and an appropriate SameSite value to limit cross-site sending. The HTTP Security Headers Analyzer reports on cookie flags.
Configuring them
# Apache (needs mod_headers)
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule># Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;An Nginx gotcha
add_header directives are inherited from an outer block only if the inner block defines none of its own. Adding one add_header in a location block silently drops the ones set at the server level. Repeat the full set where needed.
Headers to leave behind
- X-XSS-Protection: the browser auditor it controlled is gone. Use CSP.
- Expect-CT: no longer needed, since certificate transparency is enforced by browsers.
- Public-Key-Pins (HPKP): removed because it was too easy to lock yourself out.
- Revealing Server and X-Powered-By version details is a minor information leak and worth trimming, but not a substitute for patching.
Roll out in this order
- nosniff, Referrer-Policy and a framing control: low risk, high value.
- HSTS with a short max-age, then raise it.
- Permissions-Policy for features you know you do not use.
- Content-Security-Policy, starting in report-only mode.
Verify the result
Settings in your code may not match what visitors receive, because proxies and CDNs can add, change or remove headers. Run curl -I against the live URL, paste the response into the HTTP Security Headers Analyzer, and read the findings. The analyzer checks the headers you give it against common recommendations; it is not a security audit, and it cannot see things that are outside the headers.
Conclusion
A short, correct set of headers is a good foundation: HSTS, nosniff, Referrer-Policy, a framing control and a tailored CSP. Add them gradually, test on the live response, and keep the list small enough that you understand every line.
Frequently asked questions
- Which security headers matter most?
- For most sites: Strict-Transport-Security, X-Content-Type-Options: nosniff, a clickjacking control (frame-ancestors or X-Frame-Options), Referrer-Policy, and a Content-Security-Policy tailored to the site. Permissions-Policy is useful where you can restrict browser features.
- Is X-XSS-Protection still needed?
- No. The browser feature it controlled has been removed or is unsupported in modern browsers, and the header can introduce problems in older ones. Rely on a Content-Security-Policy instead; if you set it at all, the safe value is 0.
- Can security headers break my site?
- Yes, especially Content-Security-Policy and HSTS with includeSubDomains. Introduce them gradually, use report-only mode for CSP, and start HSTS with a short max-age.
- How do I check which headers my site sends?
- Run curl -I on the page or open the Network tab in your browser's developer tools, then paste the response headers into the HTTP Security Headers Analyzer.
Try it yourself
The tool that pairs with this guide.
HTTP Security Headers Analyzer
Analyze HTTP response headers for security configuration issues.
CSP Analyzer
Analyze Content Security Policy headers, detect configuration issues, explain directives and sources, and generate safer CSP recommendations.
Did this help you tighten your security headers?
If these free developer tools save you time, consider buying me a byte. Your support helps keep DevCraftKit free and growing.
Related articles
HTTP Security Headers Explained: A Practical Developer Guide
A handful of response headers tell the browser how to defend the page they came with. Here's what each one actually does.
Published
Content-Security-Policy (CSP) Explained With Examples
CSP is an allow-list for where your page's resources can load from. Here's how the directives fit together.
Published
What Is HSTS? Strict-Transport-Security Explained
HSTS closes a narrow but real window for downgrade attacks. Here's exactly what it enforces and how to configure it.
Published