Security headers checker
Enter your URL above to check the security headers your live site sends: HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options and Referrer-Policy. You get a grade from A to F, and every missing or weak header comes with the config for your host and a command to confirm the fix.
Updated
What the security headers checker tests
The checker reads the headers on your page's response, after following any redirects, and also requests the plain http version of your domain. Each check either passes or produces a finding with a severity:
- HTTPS redirect: http://your-domain should answer with a redirect to https. If it serves the page over plain http instead, that is a high severity finding.
- Strict-Transport-Security (HSTS): missing is medium. Present with a max-age under six months (15552000 seconds) is low. A max-age of one year (31536000) passes.
- Content-Security-Policy (CSP): missing on an HTML page is medium. If a policy exists but its script rules allow 'unsafe-inline' without a nonce or hash, or allow scripts from any host, it's reported as weak with low severity, because an injected script would still run. A policy whose only weakness is 'unsafe-eval' is noted as informational.
- Clickjacking protection: passes with either X-Frame-Options or a frame-ancestors directive in the CSP. Having neither is medium.
- X-Content-Type-Options: passes when set to nosniff. Missing is low.
- Referrer-Policy: missing is informational. Current browsers already default to strict-origin-when-cross-origin, so setting it is hardening rather than a fix for an active leak.
- Version headers: a Server header with a version number, or any X-Powered-By header, is reported so you can remove it. Low with a version number, informational without one.
For the CSP, the checker looks at script-src, or default-src when there is no script-src, since that's the part that decides whether injected code runs. 'unsafe-inline' in style-src is common and doesn't count against you.
How the letter grade is worked out
Every site starts with full marks. Each finding takes points off according to its severity: critical costs the most, then high, medium and low, and informational findings cost nothing. The total maps to a letter from A to F.
Two caps stop a set of passing headers from hiding a serious problem. Any critical finding, such as a live Stripe secret key in your JavaScript, keeps the grade at F. Any high finding, such as a site that still serves pages over plain http, caps it at C. Missing headers on their own can pull a site down to a C or D, but they can't produce an F by themselves.
What a strong set of security headers looks like
This is the kind of response that passes every header check. The CSP uses a nonce, a random value your app generates for each request and stamps on its own script tags:
strict-transport-security: max-age=31536000; includeSubDomains
content-security-policy: default-src 'self'; script-src 'self' 'nonce-kV3xq8ZL0pWn2bTf' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()There's no X-Powered-By line, and if a Server header is sent it has no version number. Permissions-Policy isn't graded, but it's one line and switches off browser features the site doesn't use.
The CSP is the hard part. A starter policy that still allows inline scripts is a reasonable first step and won't break anything, and the checker will keep calling it weak until script-src relies on a nonce or hashes. The per-host guides below walk through that change for Next.js, Vercel, Netlify, Cloudflare, Express and nginx.
What the report shows for each finding
The report opens with the letter grade, a count of findings at each severity and a short summary. Findings follow from most to least serious, informational ones are listed separately, and the checks that passed are listed at the end so you can see what's already right. Each finding can be read as a plain English explanation or as developer detail, and includes:
- What the problem means for the people using your site.
- The evidence: the header value, or its absence, that triggered the finding.
- The fix for your host. The checker recognises common stacks from the response (Next.js, Vercel, Netlify, Cloudflare, Express and others) and shows the config for yours first, such as next.config.ts, vercel.json or a Netlify _headers file.
- A command to confirm the fix worked, usually a one line curl.
- A prompt you can paste into Cursor, Claude Code, Lovable, Bolt, Replit or v0 that describes the problem and the change to make.
There's also a single prompt that covers every finding in order, for when you want your AI coding tool to work through the whole list.
How this differs from a header-only checker
Header graders such as securityheaders.com and the MDN HTTP Observatory are useful, and they tell you which headers are missing. This checker grades the same core headers and also looks at problems around them that a header check can't show:
- Secret keys in your page and JavaScript bundles: Stripe secret keys, OpenAI and Anthropic keys, AWS access keys, GitHub tokens, Supabase service_role and secret keys, and Google API keys that work without restrictions.
- Private files served from your domain: /.env, /.env.local, /.env.production, /.git/HEAD, /.git/config and a config.json containing credentials.
- Cookies set without the Secure flag, and login cookies readable by page scripts because HttpOnly is missing.
- CORS responses that let any website read your API with a visitor's login.
A leaked key or a public .env file is far more serious than a missing header, and the grade weighs it that way. The fixes are written for the host your site actually runs on, with a prompt for the AI tool that built it.
What the checker does not test
- It only reads what any visitor could read. It makes ordinary GET and HEAD requests, at most 30 per check, and never submits forms, logs in or sends attack payloads.
- Headers are read from the main page response. If an API route or a subpage sends different headers, check that URL with curl.
- Content-Security-Policy-Report-Only doesn't count as a CSP, because it doesn't block anything. The finding clears when you switch to the enforcing Content-Security-Policy header.
- Permissions-Policy and the cross-origin headers (Cross-Origin-Opener-Policy and related) are worth setting but aren't scored.
- It can't see your database rules, your login and permission logic, or your dependencies.
- If the site answers with a bot challenge or a login wall (a 401, 403 or 429 status), there's no real page to grade, so no report is produced.
How to check security headers yourself
You can see any site's response headers without a tool. From a terminal:
curl -sI https://your-site.comIn a browser, open DevTools, go to the Network tab, reload the page, click the first request (the page itself) and look under Response Headers. Header names are case insensitive, so strict-transport-security and Strict-Transport-Security are the same header. What neither method gives you is a judgement on whether the values are strong enough, which is what the grade adds.
Questions
What are security headers on a website?
They're HTTP response headers that tell the browser how to handle your pages: always use https (HSTS), which scripts may run (CSP), whether other sites can frame the page (X-Frame-Options), not to guess file types (nosniff) and how much of the URL to share with other sites (Referrer-Policy).
Why does my CSP show as weak when I already have one?
Its script rules still let an injected script run. The usual causes are 'unsafe-inline' in script-src without a nonce or hash, or a source like https: or * that allows scripts from any host. Moving to a per-request nonce with 'strict-dynamic' clears it.
Does checking my site put load on it?
Very little. A check makes at most 30 ordinary requests, and each site gets at most one fresh check a minute. Checking again inside that minute usually shows the saved result instead of sending new requests.
How do I fix missing security headers?
Add them where your host reads response headers: next.config.ts for Next.js, vercel.json on Vercel, a _headers file on Netlify or Cloudflare Pages, Helmet in Express, add_header in nginx. The host guides linked below have a ready block for each.