// HTTP
Security Headers Check
See which browser-side defences a site has switched on, and what each missing one costs.
// Copy-paste configuration
The seven headers this tool scores, and what each one buys you. The blocks below set all of them and score 100 against this checker.
| Header | Value | What it prevents |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Browsers refuse plain HTTP for this domain for a year after the first visit. |
| Content-Security-Policy | default-src 'self'; … | Declares where scripts and other resources may load from, so injected script has nowhere to run. |
| X-Frame-Options | DENY | Stops the page being framed by another site, which is what clickjacking needs. |
| X-Content-Type-Options | nosniff | Stops a browser reinterpreting an upload as executable script. |
| Referrer-Policy | strict-origin-when-cross-origin | Keeps full URLs, and anything sensitive in them, from leaking to third-party sites. |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | Switches off powerful browser features for the page and anything it embeds. |
| Cross-Origin-Opener-Policy | same-origin | Isolates the page from any cross-origin window that opened it. |
nginx
Inside the server block, or in a snippet included from it.
# --- Security headers -------------------------------------------------
# 'always' matters: without it these are dropped on error responses,
# which is exactly when a browser most needs them.
add_header Strict-Transport-Security "max-age=31536000; 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;
add_header Cross-Origin-Opener-Policy "same-origin" always;
# Start with Content-Security-Policy-Report-Only, then rename to enforce.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" always;
# Stop advertising the software version.
server_tokens off;// Before you paste this
- add_header does not inherit into a nested location block that declares its own add_header. If you set headers anywhere else in the config, repeat the whole set there or move them into an included snippet.
- server_tokens off shortens the Server header to nginx but does not remove it. Removing it entirely needs the headers-more module: more_clear_headers Server;
- Do not add preload until you are certain every subdomain serves HTTPS. Preloading is baked into browser binaries and removal takes months.
- The Content-Security-Policy line is a starting point, not a drop-in. If your site uses inline scripts, inline styles or third-party widgets, this policy will block them. Deploy it as Content-Security-Policy-Report-Only first, watch the reports for a week, then switch the header name to the enforcing version.
Apache
In the VirtualHost, or in .htaccess if that is all you control.
# --- Security headers -------------------------------------------------
# Requires mod_headers: a2enmod 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 X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Cross-Origin-Opener-Policy "same-origin"
# Start with Content-Security-Policy-Report-Only, then rename to enforce.
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'"
# Remove version disclosure that PHP and friends add.
Header always unset X-Powered-By
</IfModule>
# These two are server-level only. They will not work in .htaccess.
ServerTokens Prod
ServerSignature Off// Before you paste this
- Header always set overwrites any existing value. Use Header always append only where you genuinely want to add to one, which is rarely what you want for these.
- ServerTokens and ServerSignature must go in the main config. In .htaccess they are ignored, and Apache may refuse to start if you put them in the wrong context.
- Do not add preload until you are certain every subdomain serves HTTPS. Preloading is baked into browser binaries and removal takes months.
- The Content-Security-Policy line is a starting point, not a drop-in. If your site uses inline scripts, inline styles or third-party widgets, this policy will block them. Deploy it as Content-Security-Policy-Report-Only first, watch the reports for a week, then switch the header name to the enforcing version.
Cloudflare
Rules → Transform Rules → Modify Response Header, or a Worker.
// Option 1: Transform Rules (no code, applies to every response)
// Rules > Transform Rules > Modify Response Header > Create rule.
// Set the rule to apply to all incoming requests, then add one
// "Set static" entry per header:
//
// Strict-Transport-Security max-age=31536000; includeSubDomains
// X-Content-Type-Options nosniff
// X-Frame-Options DENY
// Referrer-Policy strict-origin-when-cross-origin
// Permissions-Policy camera=(), microphone=(), geolocation=()
// Cross-Origin-Opener-Policy same-origin
// Content-Security-Policy default-src 'self'; script-src 'self'; ...
//
// Option 2: a Worker, if you want the policy in version control:
const HEADERS = {
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=()",
"Cross-Origin-Opener-Policy": "same-origin",
"Content-Security-Policy":
"default-src 'self'; script-src 'self'; style-src 'self'; " +
"img-src 'self' data:; font-src 'self'; connect-src 'self'; " +
"frame-ancestors 'none'; base-uri 'self'; form-action 'self'; " +
"object-src 'none'",
};
export default {
async fetch(request) {
const response = await fetch(request);
const headers = new Headers(response.headers);
for (const [name, value] of Object.entries(HEADERS)) {
headers.set(name, value);
}
headers.delete("X-Powered-By");
// Spreading a Response does not copy status, so set it explicitly.
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
},
};// Before you paste this
- Cloudflare's own HSTS setting lives under SSL/TLS → Edge Certificates → HTTP Strict Transport Security. If you enable it there, do not also set the header here. You will end up with two values and browsers will use the first.
- Transform Rules run at the edge and apply to cached responses too, which a Worker in front of the cache may not. Prefer Transform Rules unless you need the logic.
- The Worker runs on every request and counts against your request quota. The Transform Rules route does not.
- Do not add preload until you are certain every subdomain serves HTTPS. Preloading is baked into browser binaries and removal takes months.
- The Content-Security-Policy line is a starting point, not a drop-in. If your site uses inline scripts, inline styles or third-party widgets, this policy will block them. Deploy it as Content-Security-Policy-Report-Only first, watch the reports for a week, then switch the header name to the enforcing version.
// What HTTP security headers actually do
When a server answers a request it sends headers alongside the content. A handful of them are safety instructions: insist on HTTPS from now on, restrict where scripts may load from, refuse to be embedded in someone else's page, stop guessing at content types. None of them cost anything to send, and each one removes a category of attack that would otherwise be available.
Strict-Transport-Security is the highest-value of the set. It tells the browser to refuse plain HTTP for this domain for a fixed period, closing the window in which a first request over HTTP can be intercepted and redirected somewhere else. Six months is the usual minimum, because the header is worth very little with a short max-age.
Content-Security-Policy is the most powerful and the most work. It declares where scripts, styles, images and connections may come from, so that injected script has nowhere to run from. It is also the header most likely to break a working site, which is why it is normally deployed in report-only mode first and tightened once the reports go quiet. A policy containing 'unsafe-inline' still helps, but it gives up most of the protection against cross-site scripting.
The rest are one-liners. X-Frame-Options, or CSP frame-ancestors, stops clickjacking. X-Content-Type-Options: nosniff stops a browser reinterpreting an uploaded file as executable script. Referrer-Policy stops full URLs, including anything sensitive sitting in a query string, being handed to third-party sites when someone clicks away. Permissions-Policy switches off camera, microphone and geolocation for anything you embed.
A grade here measures configuration, not safety. A site with perfect headers can still have an injection bug, and a site with none may be carefully written. What the grade tells you is how much cheap defence-in-depth is currently sitting on the table unclaimed.
// Frequently asked questions
- Will adding a Content-Security-Policy break my site?
- It can, if you rely on inline scripts or third-party widgets. Deploy it as Content-Security-Policy-Report-Only first, watch what gets reported for a week or two, then switch to enforcing once the reports are clean.
- Which header should I add first?
- Strict-Transport-Security and X-Content-Type-Options. Both are a single line, neither breaks anything, and together they close off protocol-downgrade attacks and MIME confusion. Add X-Frame-Options next, then work up to a CSP.
- Do security headers help SEO?
- Not directly. Serving over HTTPS is a ranking signal; the headers themselves are not. They matter for user safety and for passing security reviews, not for rankings.
- Why does my site score badly when it is behind a CDN?
- Most CDNs and hosting platforms pass through whatever your origin sends and add nothing of their own. Cloudflare, Vercel, Netlify and the rest all let you set these headers in configuration. The defaults simply are not set for you.
- Is it safe to run this against a site I do not own?
- Yes. It makes one ordinary GET request and reads the response headers, exactly as a browser would, and never downloads the page body. It does not scan ports, probe for vulnerabilities or submit anything.