Security Headers Checker

Send a real request to a live URL and read back the seven response headers browsers act on. You get the value each one arrived with, the reason it matters, and the nginx, Apache, or Node line to add for anything missing.

Security header scan console

Test the page people land on, not only the root. Many stacks set headers on the app and lose them on static or cached routes. Drop the protocol and https is assumed.

Seven out of seven does not mean the site is safe

This page counts headers. It reports whether each of the seven arrived and prints the value that came back. It does not grade the value. A site sending Content-Security-Policy: default-src * scores the same as one running a nonce based policy with no unsafe keywords, and the first policy blocks nothing at all.

Treat the number as an inventory, not a grade. The interesting part of any scan is the value text under each header, which is why it is printed in full rather than reduced to a tick.

What the request looks like from the other side

The scan runs from the Toolexe server, not from your browser, because a browser cannot read the response headers of a cross origin site. Your target sees a single HEAD request with a desktop Chrome user agent string, up to five redirects followed, and a ten second ceiling on the whole exchange.

Three consequences worth knowing before you read the output:

The seven, ordered by what to fix first

1 Strict-Transport-Security
Stops the first request from ever going out over plain HTTP after one successful visit. Start at max-age=300 for a day to confirm nothing on the domain still needs HTTP, then raise it to max-age=31536000; includeSubDomains. Adding preload commits you to a browser shipped list, and getting off that list takes months, so add it last and only if every subdomain including internal ones serves HTTPS.
2 X-Content-Type-Options
One value, nosniff, no tuning, no compatibility risk. It stops the browser from second guessing your Content-Type and executing a user uploaded text file as script. If a scan shows this missing, it is the cheapest fix on the list.
3 X-Frame-Options
SAMEORIGIN or DENY keeps your pages out of an attacker's iframe. The modern replacement is the CSP directive frame-ancestors, and browsers prefer it when both are present. Keep sending the old header anyway while any traffic comes from clients predating CSP Level 2.
4 Referrer-Policy
Without it, browsers apply their own default, and Chrome and Firefox settled on strict-origin-when-cross-origin years ago. Sending it yourself removes the guesswork and stops full URLs with session tokens or reset links in the query string leaking to third parties through the Referer field.
5 Permissions-Policy
Turns off browser features your site never calls. geolocation=(), camera=(), microphone=(), payment=() is a reasonable start for a content site. Its real value is containment: an injected third party script cannot prompt for the camera on a page where the feature is switched off at the response level.
6 Content-Security-Policy
The one with teeth, and the one with a real chance of breaking your site. Ranked last of the live headers on purpose, because a rushed policy either blocks your own analytics or gets watered down to unsafe-inline and stops meaning anything. Roll it out through report only mode first.
7 X-XSS-Protection
Retired. Chrome removed the XSS Auditor in 2019, Edge dropped its filter, and Firefox never had one. The only value worth sending today is 0. It still counts toward the score above because scanners across the industry count it, which is a fair criticism of every score of this kind, this one included.

Shipping a CSP without taking the site down

Skip straight to enforcement and you find out about the broken widget from a customer. The order that works:

  1. Send Content-Security-Policy-Report-Only with the policy you want, plus a report-uri or report-to endpoint. Nothing is blocked, everything is logged.
  2. Leave it running for a full traffic cycle, at least a week. Marketing tags, payment iframes, and support chat widgets tend to load only on pages nobody tests.
  3. Read the reports and separate the two kinds of violation: your own resources on a host you forgot to list, and injected junk from a browser extension. Extension noise is heavy and should not shape the policy.
  4. Replace inline scripts with a nonce or hash. A policy carrying unsafe-inline for scripts gives up most of what CSP was for.
  5. Switch the header name to Content-Security-Policy and keep the report endpoint live so regressions surface after deploys.

A policy on the paid plan of a reporting service costs less than the outage from step one done wrong.

The header is set and the scan still says missing

This is the most common support question on any header checker, and the cause is nearly always between your app and the visitor rather than in your config file.

Reading the values, not the ticks

Once the seven come back present, the second pass is where the work is. Things worth catching in the printed values:

Where this page stops

No value grading and no letter score. Presence and text only, for the reasons at the top.

No CSP directive parser. The policy is printed as it arrived so you read it yourself. For directive level review, the Google CSP Evaluator is built for exactly that and this page is not trying to replace it.

No cookie flags. Secure, HttpOnly, and SameSite live on Set-Cookie, which a HEAD request often does not trigger, so reporting on them here would be unreliable.

No TLS inspection. Protocol versions, cipher suites, and certificate chains are a different scan. Use the SSL Certificate Checker for the certificate side.

No history. Each run is a fresh request with nothing stored, so there is no trend line and no alert when a deploy drops a header. Wire a curl -sI assertion into your pipeline for that.

Questions from people mid audit

What comes up between reading a scan result and editing a server config.

Why does the scan run from your server instead of my browser?

Browsers block scripts from reading response headers on other origins unless that origin opts in through CORS, and almost nobody does. A browser side check would work on your own site and fail everywhere else. Running the request server side reads any public URL, at the cost of not seeing anything behind a login or a firewall.

Is my URL stored or shared?

No. The URL is used to make one HEAD request and the result is returned to your page. Nothing is written to a database and no report is published. The site you test does see the request in its access log, with our server IP and a Chrome user agent string, so run scans against your own properties or ones you are authorised to test.

What counts as a good score here?

Six of seven, with X-XSS-Protection set to 0, is a sensible target for most sites. Getting there without CSP is realistic for a static marketing site. Anything handling authenticated sessions should treat CSP as required rather than as the last box to tick.

Do I need X-Frame-Options if my CSP already has frame-ancestors?

Browsers supporting both prefer frame-ancestors and ignore X-Frame-Options, so functionally no. Keep sending it anyway. It costs one line, it covers old clients and embedded webviews with partial CSP support, and compliance checklists still ask for it by name.

My header shows up in DevTools but the scan says it is missing.

DevTools shows the response your browser received for that exact request, including the effect of any service worker, extension, or logged in session state. The scan sends an anonymous HEAD request from a datacentre IP. Differences usually trace to a rule keyed on cookies, geography, or request method. Reproduce with curl from a machine outside your network before changing anything.

Can I check a staging site on an internal domain?

Only if it resolves publicly and accepts connections from our server. Private ranges, VPN only hosts, and localhost are unreachable and return a fetch error. For those, run curl -sI from inside the network, which gives you the same information from a machine that can actually reach the host.

Why is my Content-Security-Policy value shown twice with a comma in it?

Because the response carried the header more than once and both values are shown joined. That happens when an application framework and a proxy each add their own policy. Browsers enforce the intersection, meaning a resource has to pass every policy present, which is a frequent cause of blocking nobody can locate in a single config file. Pick one layer to own the header and remove the other.

Does adding these headers slow the site down?

The bytes are negligible next to any image on the page. The one real cost is a long CSP repeated on every response, which is why nonce based policies stay short. Compression handles the rest, since these headers repeat identically across responses and HPACK on HTTP/2 sends them once per connection.