Website report card
One graded report for a website's security headers, Content-Security-Policy, email authentication, crawlability and meta tags, with the exact fix for each problem. Paste the response headers to start; the other three inputs are optional. The example below is a small PHP site behind nginx.
Report
| Area | Grade | Score | Issues |
|---|---|---|---|
| Security headers | C | 6 medium | |
| Content-Security-Policy | C | 1 high2 medium | |
| Email authentication | B | 2 medium | |
| Crawlability | A | none | |
| Meta and social tags | C | 1 high1 medium |
Security headers
C64 / 100 · weight 30HTTP/2 200 · 10 headers · open the full security headers tool
- Medium
HSTS max-age is 183 days
Reasonable, but below the one year (31536000) the preload list requires.
Fix Send HSTS for one year on HTTPS responses. Add includeSubDomains only once every subdomain serves HTTPS.
Show nginx · Apache · Cloudflare
nginx add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Apache Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"Cloudflare SSL/TLS > Edge Certificates > HTTP Strict Transport Security (HSTS) Enable, Max Age 12 months, include subdomains if every subdomain is HTTPS - Medium
Referrer-Policy is not set
Current Chrome, Firefox and Safari default to strict-origin-when-cross-origin, which is sensible. Setting it explicitly makes the behaviour the same in every browser.
Fix Send only the origin to other sites, never the full URL.
Show nginx · Apache · Cloudflare
nginx add_header Referrer-Policy "strict-origin-when-cross-origin" always;Apache Header always set Referrer-Policy "strict-origin-when-cross-origin"Cloudflare Rules > Transform Rules > Modify Response Header Set static Referrer-Policy Value strict-origin-when-cross-origin - Medium
Permissions-Policy is missing
Optional, but a short policy such as camera=(), microphone=(), geolocation=() stops embedded third-party content from asking for those permissions.
Fix Switch off browser features the site does not use. List any feature you do use as, for example, geolocation=(self).
Show nginx · Apache · Cloudflare
nginx add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;Apache Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"Cloudflare Rules > Transform Rules > Modify Response Header Set static Permissions-Policy Value camera=(), microphone=(), geolocation=(), payment=() - Medium
Server header reveals a version: nginx/1.24.0
A version number tells an attacker which known vulnerabilities to try. Most servers can be told to omit it (nginx: server_tokens off).
Fix Hide the version number.
Show nginx · Apache · Cloudflare
nginx server_tokens off; # in the http blockApache ServerTokens Prod ServerSignature OffCloudflare Rules > Transform Rules > Modify Response Header Remove Server - Medium
x-powered-by: PHP/8.2.12
Advertises the software stack for no benefit to visitors. Remove it.
Fix Stop advertising the application stack. For PHP, expose_php = Off in php.ini removes it at the source.
Show nginx · Apache · Cloudflare
nginx fastcgi_hide_header X-Powered-By; # PHP-FPM proxy_hide_header X-Powered-By; # reverse proxyApache Header always unset X-Powered-ByCloudflare Rules > Transform Rules > Modify Response Header Remove X-Powered-By - Medium
Cookie session lacks Secure, SameSite
Secure keeps it off plain HTTP; HttpOnly hides it from JavaScript (omit only if scripts must read it); SameSite limits cross-site sending (Chromium defaults to Lax when absent).
Fix Set the missing attributes where the cookie is created. The server-level lines add them to every cookie the backend sets.
Show App · nginx · Apache
App Set-Cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Laxnginx proxy_cookie_flags ~ secure httponly samesite=lax; # nginx 1.19.3+Apache Header always edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"
2 checks passed
- Pass
X-Frame-Options: SAMEORIGIN
Other sites cannot put this page in a frame, which prevents clickjacking.
- Pass
X-Content-Type-Options: nosniff
Browsers will not guess a different content type than the one declared, which stops some upload-based attacks.
Content-Security-Policy
C66 / 100 · weight 20Content-Security-Policy header · 3 directives · open the full content-security-policy tool
- High
script-src: 'unsafe-inline' allows inline script
Any injected <script> block or onclick= attribute runs, which is exactly what CSP exists to prevent. Replace it with nonces or hashes.
Fix Give each inline script a per-response nonce and drop 'unsafe-inline'. The application has to generate the nonce; a static web server cannot.
Show CSP · HTML
CSP script-src 'nonce-{random per response}' 'strict-dynamic';HTML <script nonce="{same random value}">...</script> - Medium
object-src is 'self' (from default-src)
Recommended value is 'none'. It costs nothing on a modern site.
Fix Add object-src 'none'. Nothing on a current site needs <object> or <embed>.
Show CSP
CSP object-src 'none'; - Medium
No base-uri
base-uri does not fall back to default-src. Without it, an injected <base href> tag can redirect every relative script URL on the page to an attacker's host, bypassing nonce-based policies. Set base-uri 'none' or 'self'.
Fix Add base-uri 'self' (or 'none' if the site has no <base> tag).
Show CSP
CSP base-uri 'self'; - Note
script-src: host allowlist (1 host)
Allowlists are often bypassable: if any listed host serves JSONP endpoints, user uploads or an old copy of AngularJS, an attacker can run script from it. Nonces with 'strict-dynamic' avoid the problem.
- Note
No frame-ancestors
Other sites may embed this page in a frame (clickjacking risk) unless X-Frame-Options is set. frame-ancestors 'self' or 'none' is the modern replacement for X-Frame-Options.
Fix Add frame-ancestors 'self' to control who can frame the page.
Show CSP
CSP frame-ancestors 'self'; - Note
No form-action
form-action does not fall back to default-src, so an injected form could post to any host. form-action 'self' is a cheap addition if all forms submit to your own site.
Fix Add form-action 'self' if every form on the site submits to the site itself.
Show CSP
CSP form-action 'self'; - Note
No reporting endpoint
Without report-uri or report-to you will not hear about violations, including ones that break your own pages.
Fix Add a report-uri (or report-to) endpoint to hear about violations. Any URL that accepts a POST will do; report collection services give you one.
Show CSP
CSP report-uri /csp-report;
Email authentication
B82 / 100 · weight 203 TXT records: SPF, DMARC, DKIM · open the full email authentication tool
- Medium
DMARC: p=none: monitoring only
Mail that fails DMARC is still delivered normally. That is the right first step while you read reports; move to quarantine, then reject, once legitimate senders all pass. Google and Yahoo require at least p=none from bulk senders.
Fix Once the reports show every legitimate sender passing, move to quarantine, then reject.
Show DNS
DNS _dmarc.example.com. TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com" - Medium
DKIM: RSA key of 1024 bits
1024-bit keys still verify, but RFC 8301 recommends 2048 bits for signers, and 1024-bit RSA is within reach of well-funded attackers. Rotate to 2048.
Fix Generate a 2048-bit key under a new selector in your mail provider's settings, publish it, switch signing to it, then retire the old selector.
- Note
SPF: 2 DNS-querying terms at the top level
Each include: pulls in another record whose own lookups count toward the same limit of 10, and this tool cannot see inside them. Check the total with the record of each included domain.
2 checks passed
- Pass
SPF: ~all (softfail)
Unlisted senders softfail: most receivers accept but mark or score the message. With DMARC in place, many operators keep ~all deliberately and let DMARC decide.
- Pass
DMARC: Aggregate reports go to 1 address
If a report address is on a different domain, that domain must publish an authorisation record (RFC 7489 7.1), which report services set up for you.
Crawlability
A100 / 100 · weight 15robots.txt · 3 groups · open the full crawlability tool
- Note
Blocks GPTBot
These are AI crawlers, not search engines, so blocking them does not affect search ranking. It is a choice, not an error.
- Note
Line 15: crawl-delay: 10
Not part of RFC 9309. Bing and Yandex honour it; Google ignores it.
2 checks passed
- Pass
Googlebot and Bingbot may crawl the homepage
Tested against the path / with the RFC 9309 matching rules.
- Pass
Sitemap listed (1)
https://www.example.com/sitemap.xml
Meta and social tags
C67 / 100 · weight 1510 meta tags, 1 link tags · open the full meta and social tags tool
- High
og:image is not an absolute URL
"/images/share-card.jpg" — crawlers fetch the image directly and do not resolve relative paths. Use the full https:// URL.
Fix Use the image's full https:// URL.
Show HTML
HTML <meta property="og:image" content="https://www.example.com/share-card.png"> - Medium
Title is 86 characters
Google cuts titles by pixel width (about 600px), which is typically 50–60 characters; the rest becomes "…". Put the words that matter first.
Fix Shorten the title to about 60 characters and put the important words first.
- Note
No og:image:width / og:image:height
Optional, but declaring them lets some platforms render the card on the first share instead of after fetching the image.
- Note
No og:image:alt
Alt text for the preview image, read by screen readers on platforms that support it.
4 checks passed
- Pass
Description is 109 characters
- Pass
Canonical URL set
https://www.example.com/coffee/
- Pass
Required Open Graph tags present
og:title, og:type, og:image and og:url.
- Pass
twitter:card is summary_large_image
Method · last reviewed
What the report checks
Each area runs the same code as one of the single-purpose tools on this site, so a finding here reads exactly as it does there. Open the tool for the full detail of one area.
| Area | Reads | Weight | Standard |
|---|---|---|---|
| Security headers | HSTS, framing, nosniff, Referrer-Policy, Permissions-Policy, cookies, version leaks | 30 | RFC 6797, Fetch, RFC 6265 |
| Content-Security-Policy | The CSP header, a report-only header, or a <meta http-equiv> policy | 20 | W3C CSP Level 3 |
| Email authentication | SPF, DMARC and DKIM TXT records | 20 | RFC 7208, 7489, 6376, 8301 |
| Crawlability | robots.txt against Googlebot and Bingbot on the homepage, Sitemap lines, X-Robots-Tag | 15 | RFC 9309 |
| Meta and social tags | Title, description, canonical, robots meta, viewport, Open Graph, X card | 15 | ogp.me, Google Search Central |
How the grade is worked out
Every area is scored out of 100. The overall score is the weighted average of the areas you gave it input for; an area left empty is marked "not checked" and its weight is shared among the rest. The letter is the same scale as the headers checker: A from 90, B from 75, C from 60, D from 40, F below.
- Security headers uses the headers checker's published weighting (HSTS 25, CSP 25, framing 15, nosniff 15, Referrer-Policy 10, Permissions-Policy 10).
- Content-Security-Policy starts at 100 and loses 20 per serious problem and 7 per warning. No policy scores 0; a report-only policy is capped at 30, because it blocks nothing.
- Email authentication scores SPF and DMARC at half each (40/40/20 when a DKIM key
record is pasted). Each record loses 35 per serious problem and 12 per warning; a
missing record scores 0, and DMARC at
p=noneis capped at 60 because it protects nothing yet. - Crawlability loses 35 per serious problem (homepage blocked, noindex header) and 10 per warning (no Sitemap line, invalid lines).
- Meta and social tags loses 25 per serious problem and 8 per warning.
These weights are this site's judgement of how much each area matters to an ordinary website, not an industry standard. Two sites with the same grade can have very different problems, which is why the findings list matters more than the letter.
Reading the fixes
Configuration fixes come as three snippets. nginx lines go in the
server block; the always keyword makes nginx add the header to error pages and
redirects too. Apache lines go in the virtual host or .htaccess and need
mod_headers. Cloudflare lines describe where to set the header in the
dashboard (a Transform Rule), which is the route when the origin server is not yours to change. DNS fixes
show the record to publish, and page fixes show the tag. Adapt the placeholders in angle brackets.
After a change, run the same command again and paste the new output. If a header you added does not
appear, something in front of the server (a CDN, a proxy, a hosting control panel) may be replacing the
headers, or nginx may be ignoring it because a location block has its own
add_header lines, which replace rather than add to the ones in the server block.
What to fix first
- Anything that hides the site:
Disallow: /for every crawler, or anoindexheader or tag on a live page. - Missing SPF or DMARC. Without them anyone can send mail that claims to be from your domain, and large mailbox providers increasingly reject or junk mail from domains without DMARC.
- The one-line headers: HSTS,
X-Content-Type-Options: nosniff, framing protection and Referrer-Policy. - The Content-Security-Policy. It protects the most and takes the most care: deploy it as
Content-Security-Policy-Report-Only, read the reports, then enforce it. - Meta and social tags, which change how the site looks in search results and shared links.
Where this report stops being reliable
- It grades one response. Other pages, API routes and static files can send different headers; check the pages that matter, such as the login page, not only the homepage.
- It cannot tell whether the headers came over HTTPS. Browsers ignore HSTS sent over plain HTTP, so paste the output for the
https://URL. - The SPF lookup count covers the top level only. Each
include:adds lookups this page cannot see, and the limit of 10 counts them all. - DKIM is only checked for the selector you paste; a domain can have several, and the selector cannot be guessed.
- Crawlability tests the homepage path against Googlebot and Bingbot. Use the robots.txt tester for any other URL or crawler.
- Meta tags are read from the HTML as delivered. Tags that JavaScript adds after the page loads are not seen here, and some crawlers do not see them either.
- Nothing here tests the TLS setup, the certificate chain or the application itself. For the certificate, use the certificate decoder.
Common questions
Why can I not just enter my URL?
A web page is not allowed to read another site's response headers: the browser's same-origin and CORS rules block it, and WebPeeks deliberately has no server to fetch sites on your behalf. So you run one command (curl -sI https://your-site/) and paste the output. Nothing you paste is sent anywhere, which also means you can check a staging site, an internal host or a change you have not deployed yet.
How is this different from a security headers scanner?
Header scanners grade one thing: the response headers. This report adds the three other things that usually go wrong on a small site: email authentication (SPF, DMARC, DKIM), crawlability (robots.txt and X-Robots-Tag), and the meta and social tags. It also reads each value rather than only checking that a header exists, and gives the configuration line to fix each finding.
Does an A mean my website is secure?
No. The report checks configuration you can see from outside: headers, DNS records and page tags. A site with an A can still have a vulnerable plugin, a weak admin password or an injectable form. What an A does say is that the protections that cost almost nothing to switch on are switched on.
What should I fix first?
Anything that stops the site being found or trusted: a Disallow: / or noindex on a live site, a missing SPF or DMARC record, or a missing HSTS or X-Content-Type-Options header. Those are one-line changes. A Content-Security-Policy without 'unsafe-inline' is worth more but takes longer, because inline scripts need nonces; start it as report-only.
Do I have to paste all four inputs?
Only the headers are required. Each optional input adds an area to the report; an area you leave empty is marked "not checked" and does not count toward the overall grade, so leaving out the DNS records does not lower the score.
Does blocking AI crawlers in robots.txt lower the grade?
No. GPTBot, ClaudeBot, CCBot and Google-Extended are not search engine crawlers, so blocking them does not affect search results. The report notes the block and does not deduct for it. Blocking Googlebot or Bingbot from the homepage does cost points.
How do I share the report?
Use "Copy as Markdown" above the report. It copies the overall grade, the grade per area and the list of things to fix, with the first configuration line for each, as Markdown that pastes cleanly into a ticket, a pull request or a chat message. The report is not stored anywhere, so there is no link to share.