What are security headers? What each one does and how to add them
Security headers switch on protections built into every browser. What each one does, safe starting values, and how to add them on common servers and platforms.
Security headers are HTTP response headers that tell the browser to switch on protections it already has built in: only connect over HTTPS, refuse to run scripts from unexpected places, don't let other sites frame this page, and so on. They don't fix vulnerable code, but they make several common attacks, such as cross-site scripting, clickjacking and protocol downgrades, much harder to pull off or less damaging when they happen.
How to see which headers your site sends
- In the browser: open developer tools, go to the Network tab, reload, click the first (document) request and look at the Response Headers.
- From a terminal:
curl -I https://example.comprints the headers. - With a checker: Rudra's free website security checker checks that the site uses HTTPS, that the TLS certificate is valid and not about to expire, and whether HSTS, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and clickjacking protection are present. It also flags
ServerorX-Powered-Byheaders that reveal software version numbers.
A presence check is a starting point, not a verdict. It tells you a header exists, not that its value is good: a Content-Security-Policy that allows scripts from anywhere is present but protects very little. Read the sections below to judge the values.
The headers, one by one
Strict-Transport-Security (HSTS)
Tells the browser to use HTTPS for your domain for a set time, even if someone types http:// or follows an old link. This closes the window in which an attacker on the network could intercept the first insecure request. A common value is Strict-Transport-Security: max-age=63072000; includeSubDomains (two years).
- Start with a short
max-age, such as 300 seconds, and raise it once you're sure every page works over HTTPS. - Add
includeSubDomainsonly if every subdomain serves HTTPS, including old ones you may have forgotten. - The
preloadflag, plus submission to the browser preload list, hard-codes HTTPS for your domain into browsers. It's difficult and slow to undo, so add it last, deliberately. - Browsers only honor HSTS when it's received over HTTPS.
Content-Security-Policy (CSP)
A list of where the page may load scripts, styles, images, fonts and frames from. If an attacker manages to inject a script, a good CSP stops the browser from running it. It's the most powerful header here and the easiest to get wrong. A reasonable starting point for a simple site:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
This allows resources only from your own origin (plus inline data: images), blocks plugins, stops injected <base> tags from redirecting relative URLs, and prevents framing. Real sites use analytics, fonts, embeds and payment providers, each of which must be allowed explicitly, and inline scripts are blocked unless you allow them with a nonce or hash. Roll it out with Content-Security-Policy-Report-Only first: the browser reports violations in the console (and to a reporting endpoint if you set one) without blocking anything, so you can adjust the policy before enforcing it.
X-Content-Type-Options
X-Content-Type-Options: nosniff stops browsers from guessing a file's type and, for example, running an uploaded text file as a script. It has no configuration and rarely breaks anything, as long as your server sends correct Content-Type headers. Add it everywhere.
Clickjacking protection: frame-ancestors and X-Frame-Options
Clickjacking tricks people into clicking something on your site while it's hidden inside another site's frame. The modern control is the CSP directive frame-ancestors 'none' (never allow framing) or frame-ancestors 'self' (only your own pages). The older X-Frame-Options: DENY or SAMEORIGIN does the same job in older browsers, and sending both does no harm. If your pages must be embedded on specific partner sites, list those origins in frame-ancestors.
Referrer-Policy
Controls how much of the current URL is sent to other sites when visitors follow a link or load a resource. URLs can contain search terms, IDs or tokens you don't want to leak. Referrer-Policy: strict-origin-when-cross-origin sends the full URL within your site but only the origin (https://example.com) to other sites, and nothing when going from HTTPS to HTTP. Modern browsers already use this as their default; setting it explicitly makes the behavior consistent.
Permissions-Policy
Turns off powerful browser features your site doesn't use, so that neither your code nor an embedded third party can request them. For a site that doesn't need the camera, microphone or location: Permissions-Policy: camera=(), microphone=(), geolocation=().
Headers to remove or skip
- Version-revealing headers.
ServerandX-Powered-Byvalues with version numbers tell attackers exactly which software and version to target. Remove them or strip the version (in nginx,server_tokens off;). - X-XSS-Protection controlled a filter that modern browsers have removed. Don't rely on it; omit it or set it to
0.
How to add security headers
nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;add_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header X-Frame-Options "DENY" always;
The always flag adds the header to error responses too. Watch out for inheritance: if a location block contains any add_header directive, it stops inheriting the ones defined at server level, so headers silently disappear from those URLs. Repeat them, or keep them in a shared file you include.
Apache
With mod_headers enabled, in the virtual host or .htaccess: Header always set X-Content-Type-Options "nosniff", and the same pattern for each header, such as Header always set Referrer-Policy "strict-origin-when-cross-origin".
Next.js
In next.config.js, return the headers for every path: async headers() { return [{ source: '/:path*', headers: [{ key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }] }]; }. For a CSP with nonces, generate the header per request in middleware instead, as the Next.js documentation describes.
Netlify, Cloudflare Pages and similar hosts
These hosts read a _headers file in your published folder. Write a path pattern on one line, such as /*, then each header on its own indented line below it, such as X-Content-Type-Options: nosniff. On Vercel, the equivalent is the headers section of vercel.json. If you use a CDN, it can usually add headers too.
Roll them out safely
- Add the low-risk headers first:
X-Content-Type-Options,Referrer-Policy,Permissions-Policyand clickjacking protection. - Enable HSTS with a short
max-age, then increase it over a few weeks. - Deploy CSP in report-only mode, fix what it reports, then enforce it.
- Test the journeys that involve third parties: login, checkout and payment frames, embedded videos, maps, chat widgets and analytics.
- Re-check with the security checker, and again whenever you add a new third-party tool.
Common mistakes
- Copying a strict CSP from another site and breaking payments, analytics or embeds.
- Writing a CSP full of
'unsafe-inline','unsafe-eval'and wildcards, which passes a presence check but protects little. - Adding HSTS
preloadbefore every subdomain is ready for HTTPS. - Setting headers in an HTML
<meta>tag. Only some CSP directives work that way; HSTS,frame-ancestorsandX-Frame-Optionsare ignored in meta tags. - Adding headers in both the application and the proxy, so responses carry duplicate or conflicting values.
- Treating headers as a substitute for updating software, validating input and escaping output.
What headers and header checks don't cover
Headers are one layer. A header check won't find an outdated plugin, a weak admin password, an injection flaw or an exposed backup file. Cookies deserve their own review too: session cookies should carry the Secure, HttpOnly and SameSite attributes. Rudra doesn't currently inspect cookies, so check them in your browser's developer tools (the Application or Storage tab). For a broader view of your site's health, run a full website audit.
Frequently asked questions
Which security header should I add first?
Make sure the whole site is served over HTTPS, then add X-Content-Type-Options: nosniff, a Referrer-Policy and clickjacking protection, which rarely break anything. Add HSTS with a short max-age next, and Content-Security-Policy last, in report-only mode first.
Do security headers affect SEO?
Not directly. HTTPS itself is a lightweight Google ranking signal, but headers like CSP or X-Content-Type-Options don't influence rankings. They protect your visitors and your site's reputation, which matters more.
Is X-Frame-Options obsolete?
It has been superseded by the CSP frame-ancestors directive, which is more flexible. Sending both is harmless and still covers older browsers.
Can I set security headers with a meta tag?
Only partly. A Content-Security-Policy can be set in a meta tag, but without frame-ancestors or reporting. HSTS, X-Frame-Options and most other security headers only work as real HTTP response headers.
Will a strict Content-Security-Policy break my site?
It can, if it blocks scripts, styles or frames your pages need. That's why you should start with Content-Security-Policy-Report-Only, review the reported violations, adjust the policy and only then enforce it.