Skip to main content
Security
Published 9 min read

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

HeaderSafe starting valuePurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomainsAlways use HTTPS
X-Content-Type-OptionsnosniffStop MIME type guessing
Referrer-Policystrict-origin-when-cross-originLimit what is leaked in the Referer header
Content-Security-PolicyTailored to your siteRestrict where resources can load from
frame-ancestors (in CSP) or X-Frame-Optionsframe-ancestors 'none' or DENY / SAMEORIGINPrevent clickjacking
Permissions-Policycamera=(), 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

  1. nosniff, Referrer-Policy and a framing control: low risk, high value.
  2. HSTS with a short max-age, then raise it.
  3. Permissions-Policy for features you know you do not use.
  4. 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.

The tool that pairs with this guide.

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.

Buy me a byte 💾