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 http://www.mozilla.org/

Checked live lookup124 ms

JSONCheck again

Grade C

Some real gaps

https://www.mozilla.org/ scores 65 out of 100. 5 of 9 headers are in place; the biggest gap is Content-Security-Policy.

HTTPSAfter 1 redirect
Grade
C

65 out of 100

Where the points are
  • Content-Security-Policy6.3 of 25
  • Strict-Transport-Security20 of 20
  • X-Frame-Options or frame-ancestors12 of 12
  • X-Content-Type-Options12 of 12
  • Referrer-Policy11 of 11
  • Permissions-Policy0 of 10
  • Cross-Origin-Opener-Policy4 of 4
  • Cross-Origin-Embedder-Policy0 of 3
  • Cross-Origin-Resource-Policy0 of 3

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

Headers in place
5of 9

4 headers could be better

  • Graded the final response, https://www.mozilla.org/. The redirect before it was not graded.
Headers9

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

Content-Security-Policy

Weak6.3 of 25 points

A Content-Security-Policy is set, but it has 1 serious gap: see the CSP tab.

Sent: base-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execut…

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.

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=()

add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

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

add_header Cross-Origin-Embedder-Policy "require-corp" always;

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

add_header Cross-Origin-Resource-Policy "same-origin" always;

Strict-Transport-Security

Good20 of 20 points

HSTS is on for 365 days; includeSubDomains is not set (add it once every subdomain works over HTTPS); preload is not set (optional).

Sent: max-age=31536000

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.

X-Frame-Options or frame-ancestors

Good12 of 12 points

Framing is limited by the CSP frame-ancestors directive, which replaces X-Frame-Options in current browsers.

Sent: frame-ancestors 'none'

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).

X-Content-Type-Options

Good12 of 12 points

MIME sniffing is disabled.

Sent: nosniff

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.

Referrer-Policy

Good11 of 11 points

strict-origin-when-cross-origin keeps full URLs from leaking to other sites.

Sent: strict-origin-when-cross-origin

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.

Cross-Origin-Opener-Policy

Good4 of 4 points

Windows from other sites cannot reach this page (same-origin).

Sent: same-origin

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.

Content-Security-Policy4

This policy is enforced: the browser blocks what it does not allow.

  • Serious Scripts may be 'unsafe-inline': any script an attacker injects into the page will run. Fix: Remove 'unsafe-inline' and allow your inline scripts with a per-response nonce or a hash.
  • Worth fixing Script sources use a wildcard host ("*.google-analytics.com", "*.googletagmanager.com"): every subdomain is trusted, including ones you do not control. Fix: Name the specific hosts instead of a wildcard.
  • Worth fixing Scripts may use 'unsafe-eval': eval() and similar calls can turn injected text into running code. Fix: Remove 'unsafe-eval' and replace eval-style code in your scripts or libraries.
  • Minor Styles may be 'unsafe-inline'. That is a smaller risk than for scripts, but injected CSS can still leak page content.
  • Note upgrade-insecure-requests is set: mixed-content requests are upgraded to https.
DirectiveAllowed sources
base-uri'none'
script-src'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com
img-src'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org
connect-src'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org https://gtm.mozilla.org o1069899.ingest.sentry.io o1069899.sentry.io region1.google-analytics.com
default-src'self' *.mozilla.org
frame-src'self' accounts.firefox.com gtm.mozilla.org https://gtm.mozilla.org js.stripe.com www.google-analytics.com www.googletagmanager.com www.youtube.com
style-src'self' 'unsafe-inline' www.mozilla.org
frame-ancestors'none'
object-src'none'
media-src'self' assets.mozilla.net videos.cdn.mozilla.net www.mozilla.org
font-src'self' www.mozilla.org
upgrade-insecure-requests(none)
form-action'self' https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org
Response headers26
Final response · 200
HeaderValue
connectionclose
content-length8054
strict-transport-securitymax-age=31536000
x-content-type-optionsnosniff
x-backend-serverbedrock-79dc4bb4bd-d5pnx.gcp-eu-west1
expiresSat, 14 Nov 2026 17:44:42 GMT
x-frame-optionsDENY
via1.1 google, 1.1 varnish, 1.1 varnish
content-security-policy-report-onlybase-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org https://gtm.mozilla.org o1069899.ingest.sentry.io o1069899.sentry.io region1.google-analytics.com; default-src 'self' *.mozilla.org; frame-src 'self' accounts.firefox.com gtm.mozilla.org https://gtm.mozilla.org js.stripe.com www.google-analytics.com www.googletagmanager.com www.youtube.com; style-src 'self' 'unsafe-inline' www.mozilla.org; frame-ancestors 'none'; object-src 'none'; media-src 'self' assets.mozilla.net videos.cdn.mozilla.net www.mozilla.org; font-src 'self' www.mozilla.org; upgrade-insecure-requests; form-action 'self' https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org
content-typetext/html; charset=utf-8
content-security-policybase-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org https://gtm.mozilla.org o1069899.ingest.sentry.io o1069899.sentry.io region1.google-analytics.com; default-src 'self' *.mozilla.org; frame-src 'self' accounts.firefox.com gtm.mozilla.org https://gtm.mozilla.org js.stripe.com www.google-analytics.com www.googletagmanager.com www.youtube.com; style-src 'self' 'unsafe-inline' www.mozilla.org; frame-ancestors 'none'; object-src 'none'; media-src 'self' assets.mozilla.net videos.cdn.mozilla.net www.mozilla.org; font-src 'self' www.mozilla.org; upgrade-insecure-requests; form-action 'self' https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org
cross-origin-opener-policysame-origin
servergranian
etag"6084d2a6c126197371d3f4220c6f4575"
cache-controlmax-age=600
referrer-policystrict-origin-when-cross-origin
x-clacks-overheadGNU Terry Pratchett
content-encodingbr
accept-rangesbytes
age583
dateSat, 14 Nov 2026 17:44:25 GMT
x-served-bycache-fra-eddf8230229-FRA, cache-fra-eddf8230229-FRA, cache-fra-etou8220186-FRA
x-cacheMISS, HIT
x-cache-hits0, 0
x-timerS1794678265.165023,VS0,VE27
varyAccept-Language, Accept-Encoding
Structured result (JSON) · 16 KB
{
  "url": "http://www.mozilla.org/",
  "finalUrl": "https://www.mozilla.org/",
  "status": 200,
  "https": true,
  "platform": "nginx",
  "hops": [
    {
      "url": "http://www.mozilla.org/",
      "status": 301,
      "ms": 21
    },
    {
      "url": "https://www.mozilla.org/",
      "status": 200,
      "ms": 97
    }
  ],
  "redirects": {
    "count": 1,
    "loop": false,
    "httpsUpgrade": true,
    "downgrade": false,
    "crossDomain": false,
    "chain": [
      {
        "from": "http://www.mozilla.org/",
        "to": "https://www.mozilla.org/",
        "status": 301
      }
    ]
  },
  "totalMs": 120,
  "grade": "C",
  "score": 65,
  "scoring": {
    "earned": 65,
    "penalty": 0
  },
  "checks": [
    {
      "id": "csp",
      "header": "Content-Security-Policy",
      "present": true,
      "rating": "weak",
      "weight": 25,
      "points": 6.3,
      "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": "A Content-Security-Policy is set, but it has 1 serious gap: see the CSP tab.",
      "value": "base-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execut…"
    },
    {
      "id": "hsts",
      "header": "Strict-Transport-Security",
      "present": true,
      "rating": "good",
      "weight": 20,
      "points": 20,
      "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": "HSTS is on for 365 days; includeSubDomains is not set (add it once every subdomain works over HTTPS); preload is not set (optional).",
      "value": "max-age=31536000"
    },
    {
      "id": "frame-protection",
      "header": "X-Frame-Options or frame-ancestors",
      "present": true,
      "rating": "good",
      "weight": 12,
      "points": 12,
      "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": "Framing is limited by the CSP frame-ancestors directive, which replaces X-Frame-Options in current browsers.",
      "value": "frame-ancestors 'none'"
    },
    {
      "id": "content-type-options",
      "header": "X-Content-Type-Options",
      "present": true,
      "rating": "good",
      "weight": 12,
      "points": 12,
      "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": "MIME sniffing is disabled.",
      "value": "nosniff"
    },
    {
      "id": "referrer-policy",
      "header": "Referrer-Policy",
      "present": true,
      "rating": "good",
      "weight": 11,
      "points": 11,
      "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": "strict-origin-when-cross-origin keeps full URLs from leaking to other sites.",
      "value": "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": "add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;"
      }
    },
    {
      "id": "coop",
      "header": "Cross-Origin-Opener-Policy",
      "present": true,
      "rating": "good",
      "weight": 4,
      "points": 4,
      "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": "Windows from other sites cannot reach this page (same-origin).",
      "value": "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": "add_header Cross-Origin-Embedder-Policy \"require-corp\" always;"
      }
    },
    {
      "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": "add_header Cross-Origin-Resource-Policy \"same-origin\" always;"
      }
    }
  ],
  "extras": [],
  "duplicates": [],
  "csp": {
    "mode": "enforced",
    "policyCount": 1,
    "directives": [
      {
        "name": "base-uri",
        "sources": [
          "'none'"
        ]
      },
      {
        "name": "script-src",
        "sources": [
          "'self'",
          "'unsafe-eval'",
          "'unsafe-inline'",
          "*.google-analytics.com",
          "*.googletagmanager.com",
          "google-analytics.com",
          "googletagmanager.com",
          "https://gtm.mozilla.org",
          "js.stripe.com",
          "s.ytimg.com",
          "tagmanager.google.com",
          "www.mozilla.org",
          "www.youtube.com"
        ]
      },
      {
        "name": "img-src",
        "sources": [
          "'self'",
          "blog.mozilla.org",
          "data:",
          "https://gtm.mozilla.org",
          "images.ctfassets.net",
          "www.google-analytics.com",
          "www.googletagmanager.com",
          "www.mozilla.org"
        ]
      },
      {
        "name": "connect-src",
        "sources": [
          "'self'",
          "*.analytics.google.com",
          "*.google-analytics.com",
          "*.googletagmanager.com",
          "analytics.google.com",
          "google-analytics.com",
          "googletagmanager.com",
          "gtm.mozilla.org",
          "https://abdri3ttkb.execute-api.us-east-2.amazonaws.com",
          "https://accounts.firefox.com/",
          "https://basket.mozilla.org",
          "https://gtm.mozilla.org",
          "o1069899.ingest.sentry.io",
          "o1069899.sentry.io",
          "region1.google-analytics.com"
        ]
      },
      {
        "name": "default-src",
        "sources": [
          "'self'",
          "*.mozilla.org"
        ]
      },
      {
        "name": "frame-src",
        "sources": [
          "'self'",
          "accounts.firefox.com",
          "gtm.mozilla.org",
          "https://gtm.mozilla.org",
          "js.stripe.com",
          "www.google-analytics.com",
          "www.googletagmanager.com",
          "www.youtube.com"
        ]
      },
      {
        "name": "style-src",
        "sources": [
          "'self'",
          "'unsafe-inline'",
          "www.mozilla.org"
        ]
      },
      {
        "name": "frame-ancestors",
        "sources": [
          "'none'"
        ]
      },
      {
        "name": "object-src",
        "sources": [
          "'none'"
        ]
      },
      {
        "name": "media-src",
        "sources": [
          "'self'",
          "assets.mozilla.net",
          "videos.cdn.mozilla.net",
          "www.mozilla.org"
        ]
      },
      {
        "name": "font-src",
        "sources": [
          "'self'",
          "www.mozilla.org"
        ]
      },
      {
        "name": "upgrade-insecure-requests",
        "sources": []
      },
      {
        "name": "form-action",
        "sources": [
          "'self'",
          "https://abdri3ttkb.execute-api.us-east-2.amazonaws.com",
          "https://accounts.firefox.com/",
          "https://basket.mozilla.org"
        ]
      }
    ],
    "findings": [
      {
        "severity": "high",
        "code": "unsafe-inline",
        "message": "Scripts may be 'unsafe-inline': any script an attacker injects into the page will run.",
        "fix": "Remove 'unsafe-inline' and allow your inline scripts with a per-response nonce or a hash."
      },
      {
        "severity": "medium",
        "code": "wildcard-script-host",
        "message": "Script sources use a wildcard host (\"*.google-analytics.com\", \"*.googletagmanager.com\"): every subdomain is trusted, including ones you do not control.",
        "fix": "Name the specific hosts instead of a wildcard."
      },
      {
        "severity": "medium",
        "code": "unsafe-eval",
        "message": "Scripts may use 'unsafe-eval': eval() and similar calls can turn injected text into running code.",
        "fix": "Remove 'unsafe-eval' and replace eval-style code in your scripts or libraries."
      },
      {
        "severity": "low",
        "code": "style-unsafe-inline",
        "message": "Styles may be 'unsafe-inline'. That is a smaller risk than for scripts, but injected CSS can still leak page content."
      },
      {
        "severity": "info",
        "code": "upgrade-insecure-requests",
        "message": "upgrade-insecure-requests is set: mixed-content requests are upgraded to https."
      }
    ]
  },
  "cookies": [],
  "server": {
    "software": "granian",
    "cdn": "Fastly",
    "versionDisclosed": false
  },
  "headers": [
    {
      "name": "connection",
      "value": "close"
    },
    {
      "name": "content-length",
      "value": "8054"
    },
    {
      "name": "strict-transport-security",
      "value": "max-age=31536000"
    },
    {
      "name": "x-content-type-options",
      "value": "nosniff"
    },
    {
      "name": "x-backend-server",
      "value": "bedrock-79dc4bb4bd-d5pnx.gcp-eu-west1"
    },
    {
      "name": "expires",
      "value": "Sat, 14 Nov 2026 17:44:42 GMT"
    },
    {
      "name": "x-frame-options",
      "value": "DENY"
    },
    {
      "name": "via",
      "value": "1.1 google, 1.1 varnish, 1.1 varnish"
    },
    {
      "name": "content-security-policy-report-only",
      "value": "base-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org https://gtm.mozilla.org o1069899.ingest.sentry.io o1069899.sentry.io region1.google-analytics.com; default-src 'self' *.mozilla.org; frame-src 'self' accounts.firefox.com gtm.mozilla.org https://gtm.mozilla.org js.stripe.com www.google-analytics.com www.googletagmanager.com www.youtube.com; style-src 'self' 'unsafe-inline' www.mozilla.org; frame-ancestors 'none'; object-src 'none'; media-src 'self' assets.mozilla.net videos.cdn.mozilla.net www.mozilla.org; font-src 'self' www.mozilla.org; upgrade-insecure-requests; form-action 'self' https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org"
    },
    {
      "name": "content-type",
      "value": "text/html; charset=utf-8"
    },
    {
      "name": "content-security-policy",
      "value": "base-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' *.google-analytics.com *.googletagmanager.com google-analytics.com googletagmanager.com https://gtm.mozilla.org js.stripe.com s.ytimg.com tagmanager.google.com www.mozilla.org www.youtube.com; img-src 'self' blog.mozilla.org data: https://gtm.mozilla.org images.ctfassets.net www.google-analytics.com www.googletagmanager.com www.mozilla.org; connect-src 'self' *.analytics.google.com *.google-analytics.com *.googletagmanager.com analytics.google.com google-analytics.com googletagmanager.com gtm.mozilla.org https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org https://gtm.mozilla.org o1069899.ingest.sentry.io o1069899.sentry.io region1.google-analytics.com; default-src 'self' *.mozilla.org; frame-src 'self' accounts.firefox.com gtm.mozilla.org https://gtm.mozilla.org js.stripe.com www.google-analytics.com www.googletagmanager.com www.youtube.com; style-src 'self' 'unsafe-inline' www.mozilla.org; frame-ancestors 'none'; object-src 'none'; media-src 'self' assets.mozilla.net videos.cdn.mozilla.net www.mozilla.org; font-src 'self' www.mozilla.org; upgrade-insecure-requests; form-action 'self' https://abdri3ttkb.execute-api.us-east-2.amazonaws.com https://accounts.firefox.com/ https://basket.mozilla.org"
    },
    {
      "name": "cross-origin-opener-policy",
      "value": "same-origin"
    },
    {
      "name": "server",
      "value": "granian"
    },
    {
      "name": "etag",
      "value": "\"6084d2a6c126197371d3f4220c6f4575\""
    },
    {
      "name": "cache-control",
      "value": "max-age=600"
    },
    {
      "name": "referrer-policy",
      "value": "strict-origin-when-cross-origin"
    },
    {
      "name": "x-clacks-overhead",
      "value": "GNU Terry Pratchett"
    },
    {
      "name": "content-encoding",
      "value": "br"
    },
    {
      "name": "accept-ranges",
      "value": "bytes"
    },
    {
      "name": "age",
      "value": "583"
    },
    {
      "name": "date",
      "value": "Sat, 14 Nov 2026 17:44:25 GMT"
    },
    {
      "name": "x-served-by",
      "value": "cache-fra-eddf8230229-FRA, cache-fra-eddf8230229-FRA, cache-fra-etou8220186-FRA"
    },
    {
      "name": "x-cache",
      "value": "MISS, HIT"
    },
    {
      "name": "x-cache-hits",
      "value": "0, 0"
    },
    {
      "name": "x-timer",
      "value": "S1794678265.165023,VS0,VE27"
    },
    {
      "name": "vary",
      "value": "Accept-Language, Accept-Encoding"
    }
  ],
  "issues": [],
  "notes": [
    "Graded the final response, https://www.mozilla.org/. The redirect before it was not graded."
  ],
  "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