Skip to content
LookupOwl

Security headers checker

Enter a URL and get a grade for the headers that tell browsers how to protect your visitors, with the exact header to add for every gap.

We follow redirects and grade the page you land on.

More options

Follow redirects (up to 10 hops, each checked again) and grade the final response, or stop at the first response. A redirect response is not graded.

Format of the copy-paste fix snippets: header (a plain header line), nginx, apache, caddy, cloudflare, vercel, netlify, nextjs or express.

Results for https://example.com/

Checked live lookup68 ms

JSONCheck again

Grade F

Most protections are missing

https://example.com/ scores 0 out of 100. 0 of 9 headers are in place; the biggest gap is Content-Security-Policy.

HTTPS
Grade
F

0 out of 100

Where the points are
  • Content-Security-Policy0 of 25
  • Strict-Transport-Security0 of 20
  • X-Frame-Options or frame-ancestors0 of 12
  • X-Content-Type-Options0 of 12
  • Referrer-Policy0 of 11
  • Permissions-Policy0 of 10
  • Cross-Origin-Opener-Policy0 of 4
  • Cross-Origin-Embedder-Policy0 of 3
  • Cross-Origin-Resource-Policy0 of 3

Content-Security-Policy: 0 of 25; Strict-Transport-Security: 0 of 20; X-Frame-Options or frame-ancestors: 0 of 12; X-Content-Type-Options: 0 of 12; Referrer-Policy: 0 of 11; Permissions-Policy: 0 of 10; Cross-Origin-Opener-Policy: 0 of 4; Cross-Origin-Embedder-Policy: 0 of 3; Cross-Origin-Resource-Policy: 0 of 3.

Headers in place
0of 9

9 headers could be better

Headers9

Biggest gaps first. Each header says what it is, what your response does and the exact line to send.

Content-Security-Policy

Missing0 of 25 points

No Content-Security-Policy: the browser will run any script an attacker manages to inject. Try it in report-only mode first so nothing breaks.

Tells the browser which sources of scripts, styles, images and frames the page may use. It is the strongest defence against cross-site scripting: an injected script is blocked unless the policy allows it.

Add: Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Strict-Transport-Security

Missing0 of 20 points

No HSTS header: a first visit over plain HTTP can be intercepted before the redirect to HTTPS.

Tells browsers to use HTTPS for this site, and optionally its subdomains, for a set time. It stops downgrade attacks and removes the unprotected first request over plain HTTP on repeat visits.

Add: Strict-Transport-Security: max-age=31536000; includeSubDomains

X-Frame-Options or frame-ancestors

Missing0 of 12 points

Nothing stops other sites from framing this page (clickjacking). Send X-Frame-Options or a CSP frame-ancestors directive.

Controls which sites may put the page inside a frame. Without it another site can overlay your page invisibly and trick people into clicking (clickjacking).

Add: X-Frame-Options: DENY

X-Content-Type-Options

Missing0 of 12 points

Without "nosniff" browsers may guess a different content type than the one sent.

Tells browsers to trust the declared Content-Type instead of guessing. It stops a browser deciding that an uploaded file is really a script and running it.

Add: X-Content-Type-Options: nosniff

Referrer-Policy

Missing0 of 11 points

No Referrer-Policy: browsers apply their own default (strict-origin-when-cross-origin in current ones). State it explicitly.

Controls how much of the page address is sent in the Referer header when someone follows a link or loads a resource. Full addresses can contain tokens, search terms or private paths that other sites should not see.

Add: Referrer-Policy: strict-origin-when-cross-origin

Permissions-Policy

Missing0 of 10 points

No Permissions-Policy: powerful browser features stay available to any embedded content.

Switches browser features such as the camera, microphone and location on or off for the page and its frames. It limits what a compromised script or an embedded third party can ask for.

Add: Permissions-Policy: camera=(), microphone=(), geolocation=()

Cross-Origin-Opener-Policy

Missing0 of 4 points

No Cross-Origin-Opener-Policy. It is opt-in and rarely breaks anything unless you rely on popups that talk to other sites.

Puts the page in its own browsing context group, so windows opened from other sites cannot reach it. It blocks some cross-window attacks and is one of the two headers that turn on cross-origin isolation.

Add: Cross-Origin-Opener-Policy: same-origin

Cross-Origin-Embedder-Policy

Missing0 of 3 points

No Cross-Origin-Embedder-Policy. It is opt-in, and it breaks cross-origin embeds that do not send CORP or CORS headers, so add it only when you need cross-origin isolation.

Requires every cross-origin resource the page loads to opt in to being embedded. With COOP it shields the page from side-channel leaks. It can break embeds that send no CORP or CORS headers, so test before you enforce it.

Add: Cross-Origin-Embedder-Policy: require-corp

Cross-Origin-Resource-Policy

Missing0 of 3 points

No Cross-Origin-Resource-Policy. Other sites may embed this response unless something else stops them.

Says which sites may load this response, for example only the same site. It keeps other sites from embedding your images, scripts and data, which closes a class of cross-site leaks.

Add: Cross-Origin-Resource-Policy: same-origin

Response headers12
Final response · 200
HeaderValue
dateSat, 14 Nov 2026 17:54:08 GMT
content-typetext/html; charset=utf-8
transfer-encodingchunked
connectionclose
servercloudflare
last-modifiedFri, 13 Nov 2026 18:54:38 GMT
allowGET, HEAD
age37
cf-cache-statusHIT
content-encodingbr
cf-raya5a7e134ea9b9e6c-CDG
alt-svch3=":443"; ma=86400
Structured result (JSON) · 8.7 KB
{
  "url": "https://example.com/",
  "finalUrl": "https://example.com/",
  "status": 200,
  "https": true,
  "platform": "header",
  "hops": [
    {
      "url": "https://example.com/",
      "status": 200,
      "ms": 67
    }
  ],
  "redirects": {
    "count": 0,
    "loop": false,
    "httpsUpgrade": false,
    "downgrade": false,
    "crossDomain": false,
    "chain": []
  },
  "totalMs": 67,
  "grade": "F",
  "score": 0,
  "scoring": {
    "earned": 0,
    "penalty": 0
  },
  "checks": [
    {
      "id": "csp",
      "header": "Content-Security-Policy",
      "present": false,
      "rating": "missing",
      "weight": 25,
      "points": 0,
      "what": "Tells the browser which sources of scripts, styles, images and frames the page may use.",
      "why": "It is the strongest defence against cross-site scripting: an injected script is blocked unless the policy allows it.",
      "message": "No Content-Security-Policy: the browser will run any script an attacker manages to inject. Try it in report-only mode first so nothing breaks.",
      "fix": {
        "header": "Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'",
        "name": "Content-Security-Policy",
        "value": "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'",
        "snippet": "Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
      }
    },
    {
      "id": "hsts",
      "header": "Strict-Transport-Security",
      "present": false,
      "rating": "missing",
      "weight": 20,
      "points": 0,
      "what": "Tells browsers to use HTTPS for this site, and optionally its subdomains, for a set time.",
      "why": "It stops downgrade attacks and removes the unprotected first request over plain HTTP on repeat visits.",
      "message": "No HSTS header: a first visit over plain HTTP can be intercepted before the redirect to HTTPS.",
      "fix": {
        "header": "Strict-Transport-Security: max-age=31536000; includeSubDomains",
        "name": "Strict-Transport-Security",
        "value": "max-age=31536000; includeSubDomains",
        "snippet": "Strict-Transport-Security: max-age=31536000; includeSubDomains"
      }
    },
    {
      "id": "frame-protection",
      "header": "X-Frame-Options or frame-ancestors",
      "present": false,
      "rating": "missing",
      "weight": 12,
      "points": 0,
      "what": "Controls which sites may put the page inside a frame.",
      "why": "Without it another site can overlay your page invisibly and trick people into clicking (clickjacking).",
      "message": "Nothing stops other sites from framing this page (clickjacking). Send X-Frame-Options or a CSP frame-ancestors directive.",
      "fix": {
        "header": "X-Frame-Options: DENY",
        "name": "X-Frame-Options",
        "value": "DENY",
        "snippet": "X-Frame-Options: DENY"
      }
    },
    {
      "id": "content-type-options",
      "header": "X-Content-Type-Options",
      "present": false,
      "rating": "missing",
      "weight": 12,
      "points": 0,
      "what": "Tells browsers to trust the declared Content-Type instead of guessing.",
      "why": "It stops a browser deciding that an uploaded file is really a script and running it.",
      "message": "Without \"nosniff\" browsers may guess a different content type than the one sent.",
      "fix": {
        "header": "X-Content-Type-Options: nosniff",
        "name": "X-Content-Type-Options",
        "value": "nosniff",
        "snippet": "X-Content-Type-Options: nosniff"
      }
    },
    {
      "id": "referrer-policy",
      "header": "Referrer-Policy",
      "present": false,
      "rating": "missing",
      "weight": 11,
      "points": 0,
      "what": "Controls how much of the page address is sent in the Referer header when someone follows a link or loads a resource.",
      "why": "Full addresses can contain tokens, search terms or private paths that other sites should not see.",
      "message": "No Referrer-Policy: browsers apply their own default (strict-origin-when-cross-origin in current ones). State it explicitly.",
      "fix": {
        "header": "Referrer-Policy: strict-origin-when-cross-origin",
        "name": "Referrer-Policy",
        "value": "strict-origin-when-cross-origin",
        "snippet": "Referrer-Policy: strict-origin-when-cross-origin"
      }
    },
    {
      "id": "permissions-policy",
      "header": "Permissions-Policy",
      "present": false,
      "rating": "missing",
      "weight": 10,
      "points": 0,
      "what": "Switches browser features such as the camera, microphone and location on or off for the page and its frames.",
      "why": "It limits what a compromised script or an embedded third party can ask for.",
      "message": "No Permissions-Policy: powerful browser features stay available to any embedded content.",
      "fix": {
        "header": "Permissions-Policy: camera=(), microphone=(), geolocation=()",
        "name": "Permissions-Policy",
        "value": "camera=(), microphone=(), geolocation=()",
        "snippet": "Permissions-Policy: camera=(), microphone=(), geolocation=()"
      }
    },
    {
      "id": "coop",
      "header": "Cross-Origin-Opener-Policy",
      "present": false,
      "rating": "missing",
      "weight": 4,
      "points": 0,
      "what": "Puts the page in its own browsing context group, so windows opened from other sites cannot reach it.",
      "why": "It blocks some cross-window attacks and is one of the two headers that turn on cross-origin isolation.",
      "message": "No Cross-Origin-Opener-Policy. It is opt-in and rarely breaks anything unless you rely on popups that talk to other sites.",
      "fix": {
        "header": "Cross-Origin-Opener-Policy: same-origin",
        "name": "Cross-Origin-Opener-Policy",
        "value": "same-origin",
        "snippet": "Cross-Origin-Opener-Policy: same-origin"
      }
    },
    {
      "id": "coep",
      "header": "Cross-Origin-Embedder-Policy",
      "present": false,
      "rating": "missing",
      "weight": 3,
      "points": 0,
      "what": "Requires every cross-origin resource the page loads to opt in to being embedded.",
      "why": "With COOP it shields the page from side-channel leaks. It can break embeds that send no CORP or CORS headers, so test before you enforce it.",
      "message": "No Cross-Origin-Embedder-Policy. It is opt-in, and it breaks cross-origin embeds that do not send CORP or CORS headers, so add it only when you need cross-origin isolation.",
      "fix": {
        "header": "Cross-Origin-Embedder-Policy: require-corp",
        "name": "Cross-Origin-Embedder-Policy",
        "value": "require-corp",
        "snippet": "Cross-Origin-Embedder-Policy: require-corp"
      }
    },
    {
      "id": "corp",
      "header": "Cross-Origin-Resource-Policy",
      "present": false,
      "rating": "missing",
      "weight": 3,
      "points": 0,
      "what": "Says which sites may load this response, for example only the same site.",
      "why": "It keeps other sites from embedding your images, scripts and data, which closes a class of cross-site leaks.",
      "message": "No Cross-Origin-Resource-Policy. Other sites may embed this response unless something else stops them.",
      "fix": {
        "header": "Cross-Origin-Resource-Policy: same-origin",
        "name": "Cross-Origin-Resource-Policy",
        "value": "same-origin",
        "snippet": "Cross-Origin-Resource-Policy: same-origin"
      }
    }
  ],
  "extras": [],
  "duplicates": [],
  "csp": {
    "mode": "none",
    "policyCount": 0,
    "directives": [],
    "findings": []
  },
  "cookies": [],
  "server": {
    "software": "cloudflare",
    "cdn": "Cloudflare",
    "versionDisclosed": false
  },
  "headers": [
    {
      "name": "date",
      "value": "Sat, 14 Nov 2026 17:54:08 GMT"
    },
    {
      "name": "content-type",
      "value": "text/html; charset=utf-8"
    },
    {
      "name": "transfer-encoding",
      "value": "chunked"
    },
    {
      "name": "connection",
      "value": "close"
    },
    {
      "name": "server",
      "value": "cloudflare"
    },
    {
      "name": "last-modified",
      "value": "Fri, 13 Nov 2026 18:54:38 GMT"
    },
    {
      "name": "allow",
      "value": "GET, HEAD"
    },
    {
      "name": "age",
      "value": "37"
    },
    {
      "name": "cf-cache-status",
      "value": "HIT"
    },
    {
      "name": "content-encoding",
      "value": "br"
    },
    {
      "name": "cf-ray",
      "value": "a5a7e134ea9b9e6c-CDG"
    },
    {
      "name": "alt-svc",
      "value": "h3=\":443\"; ma=86400"
    }
  ],
  "issues": [],
  "notes": [],
  "statusText": "OK"
}

What this checker looks at

Every web response starts with headers, and a handful of them are instructions to the browser about safety. They decide whether an injected script may run, whether the page can be tucked invisibly inside another site, whether the connection must stay on HTTPS and how much of the address leaks when someone clicks a link. None of them show on the page, so the only way to know they are there is to look. This tool fetches the page from LookupOwl's server, follows any redirects to the page a visitor would land on, and grades the headers of that final response.

Nine headers are scored: Content-Security-Policy, Strict-Transport-Security, frame protection (X-Frame-Options or the CSP frame-ancestors directive), X-Content-Type-Options, Referrer-Policy, Permissions-Policy and the three cross-origin headers (Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy). Beyond the grade, you also get a warning list for leaked server versions, deprecated headers that can do harm and headers sent twice with different values, plus a cookie audit that sits outside the score.

For each header the result says what it is, why it matters, what your response does and the exact line to send. The Content-Security-Policy gets its own tab, where every directive is read and every gap is explained in plain words.

How to use it

  1. Enter the addressType https://example.com/ or just example.com. A bare host is tried over HTTPS first and over plain HTTP only if HTTPS cannot be reached. Pick the page that matters: a home page and an API endpoint often send different headers.
  2. Choose a snippet formatUnder More options, pick where you will add the fixes: a plain header line, nginx, Apache, Caddy, Cloudflare Transform Rules, Vercel, Netlify, Next.js or Express. The grade does not change, only the wording of the copy-paste snippets.
  3. Work from the topFixes appear in the Headers tab, biggest gaps first. Start with the ones worth the most points, add them in your staging setup, then run the check again to see the grade move.
Shell
# The same check from a terminal, as JSON
curl "https://lookupowl.com/api/v1/tools/security-headers?url=https://example.com&platform=nginx"

How the grade is worked out

The rules are fixed and public, so the same response always gets the same grade. They are our own, loosely in the spirit of other header graders but not copied from any of them, and they say nothing about whether a site is safe overall. Each of the nine headers has a weight, and the weights add up to 100. A header is rated good (all of its weight), ok (60 percent), weak (25 percent) or missing (nothing).

HeaderPointsGood means
Content-Security-Policy25Scripts and plugins are restricted, with no unsafe-inline, wildcard or whole-scheme script sources.
Strict-Transport-Security20max-age of at least 15552000 seconds (180 days) on an HTTPS response.
Frame protection12DENY or SAMEORIGIN, or a CSP frame-ancestors list that is not a wildcard.
X-Content-Type-Options12The value nosniff.
Referrer-Policy11A policy that keeps full URLs from other sites, such as strict-origin-when-cross-origin.
Permissions-Policy10Present, with no powerful feature opened to every origin.
Cross-Origin-Opener-Policy4same-origin.
Cross-Origin-Embedder-Policy3require-corp or credentialless.
Cross-Origin-Resource-Policy3same-origin or same-site.

Points are then taken off, up to 20 in all, for three points per header sent twice with conflicting values (a conflicting X-Frame-Options already lowers its own rating instead), five for a Public-Key-Pins header or for Access-Control-Allow-Origin: * combined with credentials, two for a server version shown in Server or X-Powered-By, and one or two for an X-XSS-Protection header that switches on the retired browser filter. A response served over plain HTTP cannot score above 59. The score becomes a grade: 97 and above is A+ (and only if nothing was taken off), 90 is A, 75 is B, 60 is C, 40 is D and anything lower is F.

The three cross-origin headers are worth little on purpose. They are opt-in, and the embedder policy in particular can break a page that loads images or widgets from other sites. A site with the six classic headers done well scores 90, which is an A, and the last few points are the reward for going all the way.

Fixing what it finds

These are sensible starting values, not a copy-and-forget recipe. Each snippet in the result is the same header written for your platform.

HeaderA reasonable valueWatch out for
Strict-Transport-Securitymax-age=31536000; includeSubDomainsAdd includeSubDomains only when every subdomain works over HTTPS, and preload only when you mean it: removal from the browser list takes months.
Content-Security-Policydefault-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'Start with Content-Security-Policy-Report-Only, read the reports, then enforce. Replace unsafe-inline with nonces or hashes.
X-Frame-OptionsDENYUse SAMEORIGIN if your own pages frame each other. A CSP frame-ancestors directive does the same job and wins in current browsers.
X-Content-Type-OptionsnosniffMake sure scripts and styles are served with the right Content-Type, or they will stop loading.
Referrer-Policystrict-origin-when-cross-originUse no-referrer if nothing you link to needs to know where visitors came from.
Permissions-Policycamera=(), microphone=(), geolocation=()List only features you do not use; a feature your page needs must stay allowed.

When a header shows up twice

A common surprise is a header that appears two times, often with different values. It happens when the web server adds a header in its configuration and the application or a hosting panel adds it again, so the browser receives both. For X-Frame-Options the browsers do not agree on what a repeated or conflicting header means, and some ignore it, which silently removes the protection. The result lists every repeated header with each value it carried. The cure is to decide which layer owns the header, keep it there and remove it from the others.

What it cannot see

  • One anonymous request. The check carries no login, no stored session and no custom headers, so it grades what a first-time visitor gets. A page behind a login, or one that serves different headers to different paths, needs its own check.
  • Headers only. The page body is never read. A policy set in a <meta> tag, Subresource Integrity attributes and mixed content inside the page are all invisible here.
  • A policy is judged, not tested. The Content-Security-Policy is read directive by directive against known weak patterns. We do not load your page to see what the policy blocks, so a good grade is not proof that nothing is exposed.
  • CORS only as sent. A plain request carries no Origin, so we see the CORS headers a site sends by default and not how it answers a hostile origin.
  • Cookies sit outside the score. They are listed with their flags (names and attributes, never values) so you can review them, but a missing flag does not change the grade.

The request comes from our server, which looks up the host once, checks that every address is public and connects to that exact address. Fresh checks of any one domain are limited across all callers, and results are kept for five minutes. For the full response, timing and caching details, use the HTTP headers checker; for the certificate behind HTTPS, use the SSL checker.

Questions people ask

What is a good security headers score?

A or better means the headers that matter are all in place and set sensibly. A site with every header except a Content-Security-Policy can reach at most 75, a B, and many land in the C range. The grade is a checklist to work through, so look at which headers cost points rather than at the letter alone.

Why did my site get an F when it seems secure?

The grade only counts the headers a response sends, not how well the application is written. Many well-run sites send few of them because nobody set them up. Most of the 100 points come from three headers (a Content-Security-Policy, HSTS and frame protection), so adding those lifts the grade quickly.

Do security headers replace fixing vulnerabilities?

No. They are a second line of defence that limits the damage when something slips through, such as an injected script or a framing attack. They do not fix insecure code, weak passwords or outdated software, so treat a good grade as one layer, not a finish line.

Which header should I add first?

Start with Strict-Transport-Security and X-Content-Type-Options, which almost never break anything. Then add frame protection and a Referrer-Policy. Leave the Content-Security-Policy for last, since it is worth the most points and needs the most care: run it in report-only mode first.

Why does it say a header is sent twice?

Two layers of your setup are each adding it, such as the web server and the application, or a hosting control panel and a CDN. If the values differ, browsers may pick one, pick the last or ignore the header. Choose one layer to own the header and remove it from the other.

Is Content-Security-Policy-Report-Only enough?

It is a good first step but it does not protect anyone, because violations are reported and never blocked. The grade treats it as weak for that reason. Once the reports show nothing legitimate is being flagged, send the same policy as Content-Security-Policy.

Can I use this check from a script or an AI assistant?

Yes. The same result is available as JSON from the REST API and as the security_headers tool on the MCP server, with no key or signup. Pass the URL and, if you like, the platform you want fix snippets for. Fresh checks of one domain are limited to 30 a minute across all callers.

Use it from AI, the API or the command line

Every LookupOwl tool is also a free API and an MCP tool, with no key and no account. This one is security_headers. Please be kind to the rate limits.

curl
curl "https://lookupowl.com/api/v1/tools/security-headers?q=https%3A%2F%2Fexample.com%2F"
MCP
claude mcp add --transport http lookupowl https://lookupowl.com/mcp