# Security Headers Checker

Fetch a public http(s) URL from our server, follow its redirects (up to 10) and grade the security headers of the final response from A+ to F with published rules: Content-Security-Policy (parsed and evaluated: unsafe-inline, unsafe-eval, wildcards, missing object-src and base-uri, report-only), Strict-Transport-Security (max-age, includeSubDomains, preload), X-Frame-Options or frame-ancestors, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and Cross-Origin-Opener, Embedder and Resource Policy. Also reports leaked server versions, deprecated or harmful headers (X-XSS-Protection, Public-Key-Pins), a dangerous CORS pair, headers sent twice with conflicting values, and cookie flags. Every finding says what the header is, why it matters and the exact header line to add, optionally as a snippet for nginx, Apache, Caddy, Cloudflare, Vercel, Netlify, Next.js or Express. Response bodies are never read, so meta-tag policies and Subresource Integrity are not checked.

**Not for:** Raw header inspection and caching (use http_headers), certificates (use ssl_check) or DNS records (use dns_lookup).

- Tool id: `security-headers`
- Category: security
- Execution: network
- MCP tool `security_headers` at https://lookupowl.com/mcp
- REST `GET https://lookupowl.com/api/v1/tools/security-headers` (or POST a JSON body)
- OpenAPI: https://lookupowl.com/api/openapi.json

## Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `url` | string | yes |  | URL or host to check, e.g. https://example.com/ or example.com (https is tried first, then http). Only public http(s) addresses on ports 80, 443, 8080 and 8443 are fetched; private, loopback and internal addresses are refused. max length 2048 |
| `followRedirects` | boolean | no | `true` | 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. |
| `platform` | "header" \| "nginx" \| "apache" \| "caddy" \| "cloudflare" \| "vercel" \| "netlify" \| "nextjs" \| "express" | no | `"header"` | Format of the copy-paste fix snippets: header (a plain header line), nginx, apache, caddy, cloudflare, vercel, netlify, nextjs or express. |

## Output

Results are wrapped in an envelope: `tool`, `version`, `requestId`, `data`, `summary`, `meta` (cache status, timing), `permalink`. `data` has these fields:

| Field | Type | Description |
|---|---|---|
| `url` | string | The URL that was requested (canonical form). |
| `finalUrl` | string |  |
| `status` | integer | Status of the last response. min -9007199254740991, max 9007199254740991 |
| `statusText` | string |  |
| `https` | boolean | The final response came over HTTPS. |
| `platform` | "header" \| "nginx" \| "apache" \| "caddy" \| "cloudflare" \| "vercel" \| "netlify" \| "nextjs" \| "express" |  |
| `hops` | object[] |  |
| `redirects` | object |  |
| `totalMs` | number |  |
| `grade` | string | A+ to F, or n/a when the last response is a redirect (its headers belong to the page it points to). |
| `score` | integer | 0-100: points earned by the nine headers, minus penalties, capped at 59 over plain HTTP. min -9007199254740991, max 9007199254740991 |
| `scoring` | object |  |
| `checks` | object[] |  |
| `extras` | object[] | Warnings beyond the nine headers: leaked versions, deprecated or harmful headers, CORS and repeated headers. |
| `duplicates` | object[] | Headers sent more than once, with their values. |
| `csp` | object |  |
| `cookies` | object[] | Cookies set by the final response: names and flags only, never values. Not part of the score. |
| `server` | object |  |
| `headers` | object[] | Every response header line of the final response, in order (Set-Cookie left out). |
| `issues` | object[] |  |
| `notes` | string[] |  |

## Examples

### Grade example.com

```json
{"url":"https://example.com/","followRedirects":true,"platform":"header"}
```

```bash
curl 'https://lookupowl.com/api/v1/tools/security-headers?followRedirects=true&platform=header&url=https%3A%2F%2Fexample.com%2F'
```

Web page: https://lookupowl.com/security-headers?q=https%3A%2F%2Fexample.com%2F

### Grade github.com

```json
{"url":"https://github.com/","followRedirects":true,"platform":"header"}
```

```bash
curl 'https://lookupowl.com/api/v1/tools/security-headers?followRedirects=true&platform=header&url=https%3A%2F%2Fgithub.com%2F'
```

Web page: https://lookupowl.com/security-headers?q=https%3A%2F%2Fgithub.com%2F

### Start on http and follow the redirect

```json
{"url":"http://www.mozilla.org","followRedirects":true,"platform":"nginx"}
```

```bash
curl 'https://lookupowl.com/api/v1/tools/security-headers?followRedirects=true&platform=nginx&url=http%3A%2F%2Fwww.mozilla.org%2F'
```

Web page: https://lookupowl.com/security-headers?q=http%3A%2F%2Fwww.mozilla.org%2F&platform=nginx

## Web pages

- https://lookupowl.com/security-headers

## Limits and caching

- Cost: 3 units for a live lookup, 1 unit for a cached one. Budgets are per network and published in the `RateLimit` response headers.
- Cache: results are kept for 300 s. Add `fresh=true` (MCP argument or query parameter) to look again; it costs 3 units per unit and is limited to 10 per hour.
- Timeout: 16 s.

## Related tools

- [HTTP Headers Checker](https://lookupowl.com/tools/http-headers.md): Fetch a URL, follow its redirects and see every response header, with a security-header grade and caching summary.
- [SSL Certificate Checker](https://lookupowl.com/tools/ssl-checker.md): Check a site's SSL/TLS certificate: days until it expires, whether it is trusted, matches the host and has a complete chain.
- [DNS Lookup](https://lookupowl.com/tools/dns-lookup.md): Look up A, AAAA, MX, NS, TXT, CNAME, SOA, CAA, DS and DNSKEY records from a public resolver or the authoritative nameserver.
- [WHOIS Lookup](https://lookupowl.com/tools/whois-lookup.md): Registrar, creation and expiry dates, status codes, nameservers and DNSSEC for any domain, from RDAP with WHOIS fallback.
- [DNS Propagation Checker](https://lookupowl.com/tools/dns-propagation.md): Check whether a DNS change has reached public resolvers around the world and which caches are still stale.

## Guide: 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.

### 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 address**: Type `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 format**: Under 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 top**: Fixes 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.

```bash
# The same check from a terminal, as JSON
curl "https://lookupowl.com/api/v1/tools/security-headers?url=https://example.com&platform=nginx"
```

> **In Claude, ChatGPT or Cursor** With the LookupOwl MCP server connected, ask "Which security headers is example.com missing, and what should I add?" The assistant calls `security_headers` and answers from the graded result.

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

| Header | Points | Good means |
|---|---|---|
| `Content-Security-Policy` | 25 | Scripts and plugins are restricted, with no `unsafe-inline`, wildcard or whole-scheme script sources. |
| `Strict-Transport-Security` | 20 | `max-age` of at least 15552000 seconds (180 days) on an HTTPS response. |
| Frame protection | 12 | `DENY` or `SAMEORIGIN`, or a CSP `frame-ancestors` list that is not a wildcard. |
| `X-Content-Type-Options` | 12 | The value `nosniff`. |
| `Referrer-Policy` | 11 | A policy that keeps full URLs from other sites, such as `strict-origin-when-cross-origin`. |
| `Permissions-Policy` | 10 | Present, with no powerful feature opened to every origin. |
| `Cross-Origin-Opener-Policy` | 4 | `same-origin`. |
| `Cross-Origin-Embedder-Policy` | 3 | `require-corp` or `credentialless`. |
| `Cross-Origin-Resource-Policy` | 3 | `same-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.

| Header | A reasonable value | Watch out for |
|---|---|---|
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` | Add `includeSubDomains` only when every subdomain works over HTTPS, and `preload` only when you mean it: removal from the browser list takes months. |
| `Content-Security-Policy` | `default-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-Options` | `DENY` | Use `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-Options` | `nosniff` | Make sure scripts and styles are served with the right `Content-Type`, or they will stop loading. |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Use `no-referrer` if nothing you link to needs to know where visitors came from. |
| `Permissions-Policy` | `camera=(), 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.

> **Test before you tighten** Security headers change how pages behave. A strict Content-Security-Policy, `Cross-Origin-Embedder-Policy` or `X-Frame-Options: DENY` can break an embedded video, a payment widget or an admin page that is meant to sit in a frame. Roll changes out on a staging copy or in report-only mode first.

### 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](https://lookupowl.com/http-headers-checker); for the certificate behind HTTPS, use the [SSL checker](https://lookupowl.com/ssl-checker).

### Frequently asked questions

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