WebPeeks

Website report card

Grade a website's headers, email records and search tags in one report

Paste the output of curl -sI (plus, if you like, the page's <head>, robots.txt and SPF/DMARC records) and get a grade per area with the nginx, Apache, Cloudflare, DNS or HTML line that fixes each problem. Nothing leaves this tab.

Paste what the site sends back

The output of curl -sI for the page. A redirect chain from curl -sIL works too; the last response is graded.
Page <head> 14 lines
View source (Ctrl+U, or Cmd+Option+U on a Mac), copy from <head> to </head>.
robots.txt 16 lines
DNS TXT records 3 lines
dig output as it is, or the bare record text, one record per line.
Updates as you paste. Nothing leaves this page.

Report

74 / 100Overall grade C, 74 out of 100

www.example.com

HTTP/2 200 · 5 of 5 areas checked

2 high11 medium9 notes10 passed

Grade by area
AreaGradeScoreIssues
Security headersC646 medium
Content-Security-PolicyC661 high2 medium
Email authenticationB822 medium
CrawlabilityA100none
Meta and social tagsC671 high1 medium

Security headers

C64 / 100 · weight 30

HTTP/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 block
    Apache
    ServerTokens Prod
    ServerSignature Off
    Cloudflare
    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 proxy
    Apache
    Header always unset X-Powered-By
    Cloudflare
    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=Lax
    nginx
    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 20

Content-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 20

3 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 15

robots.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 15

10 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

Single checks

Each area of the report in full detail, plus three more decoders.

Where to get what you paste

macOS and Linux terminals; on Windows use curl.exe and nslookup -type=TXT.

To checkRun or doThen paste into
Response headers curl -sIL https://example.com/ Headers
Content-Security-Policy curl -sI https://example.com/ | grep -i content-security CSP
TLS certificate chain openssl s_client -connect example.com:443 -servername example.com -showcerts Certificate
SPF, DMARC, DKIM records dig +short TXT _dmarc.example.com SPF/DMARC
Meta and Open Graph tags View source (Ctrl+U), copy the <head> Meta tags
robots.txt curl -s https://example.com/robots.txt robots.txt

Who this is for

People who look after websites without doing it all day: the developer who also runs the server, the agency setting up a client's email, the site owner whose link previews look wrong, the person asked to "sort out the security headers" after an audit. The tools answer the question in front of you and tell you which standard the answer comes from, so you can check it and explain it to someone else.

Why you paste instead of typing a URL

Most "check my site" tools send your URL to their server, which fetches the site and reports back. WebPeeks has no server doing that, and a browser does not let one site read another site's headers or certificate (the CORS rules). So you fetch the data with one command and paste the result.

That has two useful side effects. Nothing you check passes through anyone else's server, which matters when it is an internal hostname, a staging site or a token. And you can check things that are not public yet: a robots.txt you are still drafting, a DNS record before you publish it, or the headers from a server on your own network.

How the checks are made

Each tool follows the published standard for what it reads: RFC 5280 for certificates, RFC 9309 for robots.txt, RFC 7208, 6376 and 7489 for SPF, DKIM and DMARC, the W3C CSP Level 3 specification, RFC 7519 for JWTs. Where there is no standard, as with the grades, the page says the weighting is this site's own and shows it in full. The about page lists every method and its limits in one place.