HTTP Headers Checker guide
What the XGM HTTP Headers Checker reports for HSTS, CSP, X-Frame-Options, Referrer-Policy and more, with ready-to-use fixes for nginx and other servers.
What security headers do
Every HTTP response carries headers that browsers read before rendering the page. A handful of them switch on protections that the browser cannot apply by default without breaking older sites: HTTPS-only access, restrictions on script sources, protection against being framed by another site. Sending them costs almost nothing and closes whole classes of attacks.
Headers are per response. A header set in one server block or application route does not automatically apply to others, and proxies or CDNs can add, change or remove them. That is why checking the real response from outside is more reliable than reading configuration files.
How to use the HTTP Headers Checker
- Open the HTTP Headers Checker and enter a domain, for example
example.com. - XGM requests
https://example.com, follows redirects and reads the headers of the final page. - Review the verdict and the finding per header, the CSP analysis (unsafe sources, missing directives, reporting) and the cache headers. Missing or weak headers include a snippet for nginx, Apache, Caddy and Cloudflare you can copy.
- Open "All response headers" to see the raw list, including caching and server headers.
- After changing your configuration, run the check again; the permalink keeps the domain in the URL. The Full report tab adds a scored header report with recommendations.
Because headers come from the final page, a redirect from example.com to www.example.com means you see the headers of www. Check the bare domain's redirect responses with the Redirect Checker, especially for HSTS, which should also be sent on redirects.
The headers and what the checker expects
| Header | Passes when | Why it matters |
|---|---|---|
Strict-Transport-Security | Present with max-age of at least 15552000 (180 days) | Browsers use HTTPS only, closing the first-visit HTTP gap |
Content-Security-Policy | Present without 'unsafe-inline' or 'unsafe-eval' in script-src, wildcards, or missing object-src | Main browser defence against injected scripts |
X-Content-Type-Options | nosniff | Stops browsers from guessing content types |
X-Frame-Options | Present (DENY or SAMEORIGIN) | Prevents clickjacking; frame-ancestors in CSP is the modern equivalent |
Referrer-Policy | Present and not unsafe-url | Limits how much of your URLs is sent to other sites |
Permissions-Policy | Present (optional; missing is informational) | Disables browser features such as camera or geolocation |
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;Start CSP in report-only mode
Content-Security-Policy-Report-Only first, as described in the CSP guide.Version leaks and cookie flags
Headers such as Server: nginx/1.18.0 or X-Powered-By: PHP/7.4.3 tell anyone which software versions you run. That does not create a vulnerability, but it saves attackers time when a version has known problems. The checker reports version numbers in Server, X-Powered-By and ASP.NET version headers as informational findings.
# nginx
server_tokens off;
# PHP (php.ini)
expose_php = OffCookies set by the page are checked for three flags. Secure keeps the cookie off plain HTTP, HttpOnly hides it from JavaScript, which matters for session cookies, and SameSite limits when the browser sends it with cross-site requests. A session cookie without them is easier to steal or misuse.
Set-Cookie: session=4f9c2b…; Path=/; Secure; HttpOnly; SameSite=LaxCommon mistakes
| Cause | Explanation | Fix |
|---|---|---|
nginx add_header without always | Headers are skipped on error responses | Add always |
add_header in a location block | It replaces all headers inherited from the server block | Repeat the headers or use an include file |
| CDN or proxy overrides | The edge sets its own values or strips headers | Configure headers at one layer and verify from outside |
| Headers set by the application only | Static files and error pages served by the web server lack them | Set security headers in the web server or CDN |
| Duplicate headers with different values | Browsers may apply the first or combine them | Remove duplicates |
The X-XSS-Protection header is sometimes still recommended by old checklists. Modern browsers removed the feature it controlled, and the header is no longer useful; a Content Security Policy replaces it.
Setting headers on other servers and platforms
The nginx snippets in the findings translate directly to other web servers. Set the headers once, at the layer that serves every response, and prefer the web server or CDN over application code so static files and error pages get them too.
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"example.com {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}CDNs and hosting platforms usually offer a rules or headers section in their dashboard, or a configuration file in the repository, for adding response headers. After changing them, purge cached responses if the platform caches headers along with content, then check again from outside.
Keep a short record of which layer owns each header. When a site moves to a new platform, security headers are among the settings most often lost, because nothing breaks visibly when they disappear. Running the checker after every migration catches that in seconds.
Other headers worth a look
The raw header list also shows caching and performance settings. Cache-Control decides how long browsers and CDNs keep a response, and pages with personal data should use private or no-store. Compression shows up as Content-Encoding: gzip or br, and Vary explains which request headers change the response.
Cross-origin isolation headers (Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy) matter for applications that need powerful browser features or want strict isolation from other sites. They are not required for most websites, so the checker lists them without grading them.
FAQ
Which security headers are most important?
Strict-Transport-Security and Content-Security-Policy give the biggest protection. X-Content-Type-Options and frame protection are easy additions; Referrer-Policy and Permissions-Policy tidy up privacy and features.
Why does the checker show headers from a different URL?
It follows redirects and reports the final page. If example.com redirects to www.example.com, the headers come from www.
Can I set security headers in HTML meta tags?
Only some. CSP can be partly set with a meta tag, but HSTS, X-Frame-Options and frame-ancestors must be HTTP headers.
Is X-Frame-Options still needed with CSP frame-ancestors?
Modern browsers use frame-ancestors when both are present. Sending X-Frame-Options as well is harmless and covers older browsers.
Does a perfect header score make a site secure?
No. Headers reduce the impact of certain attacks in the browser. Application security, patching and access control still matter.
Do headers need to be on every page?
Yes. Browsers apply them per response, and HSTS is refreshed on every response. Set them at the server or CDN so they reach every page, including errors.
Why does my CSP count as weak?
The checker flags 'unsafe-inline' or 'unsafe-eval' in script sources, wildcard sources, a missing default-src or script-src, and a missing object-src. Each allows a way around the protection CSP is meant to give.
Can the checker test pages behind a login?
No. It requests the public URL without cookies. Headers on logged-in pages usually match the public ones if they are set at the server.