SecHead
فحص موقعاتصل بنا
Troubleshooting12 min read

Why Your Site is Stuck at Grade B (Most Common Misses)

Getting an A on a security header scan isn't hard - but several specific issues commonly cap sites at B or C. Here's what to check.

SL
Seven Labs · 17 March 2026
2,411 words

Quick Answer If your website is stuck at Security Headers Grade B, it typically means you are missing one or two critical HTTP response headers, or your existing headers are misconfigured. The most common reasons include a missing Permissions-Policy, an absent or weak Content-Security-Policy (especially using unsafe-inline), or lacking the includeSubDomains directive in your Strict-Transport-Security (HSTS) header. To reach Grade A or A+, you must implement a strict CSP, enforce HSTS across all subdomains, and lock down browser features using Permissions-Policy.

People Also Ask

  • How do I get an A+ on SecurityHeaders.com? To achieve an A+, you need a flawless implementation of all standard security headers (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) and your HSTS header must include the preload directive alongside includeSubDomains.
  • Why is my CSP Grade so low on Mozilla Observatory? Mozilla Observatory heavily penalizes the use of 'unsafe-inline' and 'unsafe-eval' in your Content-Security-Policy (CSP). A low CSP Grade limits your overall score, commonly resulting in a Grade B or lower, even if all other headers are perfect.
  • What is a good Security Headers score? A Grade A is generally considered the industry standard for secure web applications. Grade B indicates room for improvement but basic protections are in place, while C or below signifies significant vulnerabilities.
  • Does Security Headers Grade B affect SEO? While Google doesn't explicitly use "Security Headers Grade" as a ranking factor, the security of a site (like HTTPS enforcement via HSTS) and protection against malware injection (via CSP) directly impact user experience and safety, which are core ranking signals.

Introduction to Security Headers Grade B

Achieving a stellar rating on tools like SecHead, Mozilla Observatory, or SecurityHeaders.com is a badge of honor for web developers and security engineers. However, the journey from a dismal F to a respectable B is often swift, while the leap from a Security Headers Grade B to an A or A+ can feel like hitting a brick wall.

If you are stuck at Grade B, you are not alone. A Grade B signifies that you have implemented the low-hanging fruit-perhaps X-Frame-Options, X-Content-Type-Options, and maybe a basic Referrer-Policy. But to cross the threshold into Grade A territory, scanners demand more than just participation; they demand precision, strictness, and the implementation of advanced policies that prevent sophisticated attacks like Cross-Site Scripting (XSS) and Clickjacking.

In this massive, definitive guide, we will dissect exactly why your application is plateauing at Grade B, the specific headers holding you back, the technical intricacies of the Content-Security-Policy (CSP) Grade, and how to definitively solve these issues across Nginx, Apache, and Node.js.


The Most Common Culprits Keeping You at Grade B

1. The Missing or Weak Content-Security-Policy (CSP)

The Content-Security-Policy is the heavy lifter of web security. It is designed to mitigate XSS and data injection attacks by explicitly declaring which dynamic resources are allowed to load.

The Error: You either do not have a CSP, or you have one that includes 'unsafe-inline' or 'unsafe-eval'.

🚨 WARNING: Content-Security-Policy is present but allows unsafe inline scripts. Overall grade capped at B.

If you use 'unsafe-inline' in your script-src directive, you effectively neutralize the primary benefit of CSP. Scanners like Mozilla Observatory and SecHead recognize this and will severely penalize your CSP Grade, anchoring your overall score to a B regardless of how perfect your other headers are.

The Fix: Transition to a strict CSP using nonces or hashes.

$ curl -I https://yourwebsite.com
HTTP/2 200
Server: nginx
# The BAD way (Grade B):
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'

# The GOOD way (Grade A):
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'none';

2. Strict-Transport-Security (HSTS) Missing includeSubDomains

HSTS forces browsers to communicate with your site exclusively over HTTPS, protecting against protocol downgrade attacks and cookie hijacking.

The Error: You have the HSTS header, but you forgot to protect your subdomains, or the max-age is too short.

The Fix: Your HSTS header must include max-age (set to at least 31536000 seconds, which is one year) and includeSubDomains. To aim for an A+, you must also add preload and submit your domain to the HSTS Preload List.

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

3. Forgetting the Permissions-Policy

Formerly known as Feature-Policy, the Permissions-Policy header allows site owners to enable, disable, and control the behavior of web platform features and APIs in the browser (e.g., microphone, camera, geolocation).

The Error: Because it is relatively new compared to classic headers, it is often simply forgotten. Missing it results in a point deduction that can keep you at a B.

The Fix: Implement a restrictive default Permissions-Policy.

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

4. Weak Referrer-Policy

The Referrer-Policy governs how much origin information is sent in the Referer header when a user clicks a link or loads a resource.

The Error: Setting it to unsafe-url or no-referrer-when-downgrade (which is often the default legacy behavior). unsafe-url leaks your full path and query string parameters to third-party sites.

The Fix: Use strict-origin-when-cross-origin.

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

Deep Dive: Content-Security-Policy (CSP) Grade Penalties

Why is the CSP so hard to get right? Because modern web development relies heavily on inline styles, inline scripts (think Google Analytics snippets, Tag Managers, or framework-injected code), and dynamic evaluation.

When you scan your site on Mozilla Observatory, you receive a specific CSP Grade. If your CSP Grade is low, your total score mathematically cannot reach an A.

Here is what happens in the browser when you deploy a strict CSP without testing:

URL: https://sechead.io/dashboard
SSL: Valid (Padlock)
Console:
> [Error] Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-12345'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
> [Error] Refused to load the stylesheet 'https://fonts.googleapis.com/css' because it violates the following Content Security Policy directive: "style-src 'self'".

To fix your CSP Grade and break past Grade B without breaking your site:

  1. Use Report-Only Mode First: Deploy Content-Security-Policy-Report-Only instead of blocking immediately. This allows you to monitor console logs or ingestion endpoints for violations without breaking user experience.
  2. Refactor Inline Scripts: Move JavaScript out of HTML attributes (like onclick=) and into external .js files.
  3. Implement Nonces: Generate a cryptographically secure random base64 string on every HTTP request and inject it into the <script nonce="your_nonce"> tags, matching the header value. This is the recommended approach for modern web applications.
  4. Use Hashes for Static Inline Scripts: If you have an inline script that never changes, calculate its SHA-256 hash and include it in the header.

How to Implement Fixes Across Different Web Servers

Achieving Grade A requires correctly configuring your web server or application middleware. Here are robust, copy-pasteable configurations to elevate your site from Grade B to A+.

Nginx Configuration Snippet

Add these directives within your server block or location block in nginx.conf. Ensure you carefully audit your CSP before applying it.

# HSTS (1 Year, include SubDomains, Preload)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# X-Frame-Options
add_header X-Frame-Options "SAMEORIGIN" always;

# X-Content-Type-Options
add_header X-Content-Type-Options "nosniff" always;

# Referrer-Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

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

# Content-Security-Policy (Strict example without unsafe-inline)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self';" always;

Apache Configuration Snippet

For Apache, ensure mod_headers is enabled (a2enmod headers), then add this to your .htaccess or VirtualHost configuration:

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self';"
</IfModule>

Node.js / Express (Helmet)

If you are running a Node.js application, the helmet middleware is the industry standard for setting security headers seamlessly.

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"], // Ensure no 'unsafe-inline' is here
      styleSrc: ["'self'"],
      imgSrc: ["'self'", "data:"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin'
  }
}));

// Manually add Permissions-Policy since helmet doesn't fully configure all features yet
app.use((req, res, next) => {
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=(), usb=()');
  next();
});

Troubleshooting: "I Added the Headers but I'm Still Getting a B!"

You modified your server config, restarted Nginx, ran a fresh scan on SecHead or SecurityHeaders.com, and... you're still stuck at Grade B. Why?

  1. Caching Layers and CDNs: If you use Cloudflare, AWS CloudFront, or Fastly, your edge nodes might be caching the old HTTP responses. Purge your CDN cache. Furthermore, some CDNs inject their own headers or override your origin headers.
  2. Proxy Pass Overwrites: If Nginx is acting as a reverse proxy for a Node.js application, and both are trying to set security headers, you might end up with duplicate headers, or the reverse proxy might drop the backend headers. Choose one layer to manage headers (usually the edge or reverse proxy).
  3. Typos in Header Names: Double-check your spelling. Errors like X-Frame-Option (missing the 's') or Feature-Policy (deprecated in favor of Permissions-Policy) will cause scanners to ignore them.
  4. Casing Issues: While header names are case-insensitive, values often are not. 'unsafe-inline' must be exactly that, and it must be enclosed in single quotes within the directive.
$ curl -i https://yourwebsite.com
HTTP/2 200
Server: cloudflare
# ERROR: Duplicate headers!
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY

Having duplicate headers with conflicting values often causes scanners to invalidate the header entirely. Clean up your middleware and edge configs to ensure headers are only sent once.


Frequently Asked Questions (FAQ)

1. What does Security Headers Grade B mean? A Grade B means your site has implemented several foundational security headers, but lacks comprehensive protection against complex attacks, typically due to a weak Content-Security-Policy or missing HSTS sub-domain coverage.

2. Can I get an A+ without a Content-Security-Policy? No. A Content-Security-Policy is a hard requirement for achieving an A or A+ on all major security header scanning platforms like SecHead and SecurityHeaders.com.

3. Why is 'unsafe-inline' considered bad? 'unsafe-inline' allows the execution of inline <script> tags and JavaScript event handlers (like onload or onclick). If an attacker manages to inject a malicious script tag into your page (XSS), the browser will execute it. A strict CSP blocks this entirely.

4. How do I fix the CSP Grade on Mozilla Observatory? To improve your CSP Grade, you must remove 'unsafe-inline' and 'unsafe-eval' from your script-src, define object-src 'none', and set a base-uri.

5. What is the difference between SecHead and SecurityHeaders.com? Both are reputable scanners. SecHead offers a slightly more modern ruleset that aggressively flags deprecated headers (like X-XSS-Protection) and emphasizes Permissions-Policy, while SecurityHeaders.com is a well-established legacy standard.

6. Should I use X-XSS-Protection? No. X-XSS-Protection is deprecated and can actually introduce vulnerabilities in certain edge cases. Modern browsers ignore it. Scanners will not penalize you for omitting it, and some may warn you if you include it.

7. How do I enable HSTS safely? Start with a short max-age (e.g., 5 minutes: max-age=300) to test. Once you confirm your site works flawlessly over HTTPS and no mixed content errors occur, gradually increase the max-age to 1 year (max-age=31536000).

8. What happens if I set HSTS includeSubDomains but an old subdomain only has HTTP? That subdomain will break. Browsers that have visited your main site will forcefully try to load the HTTP subdomain over HTTPS. If it doesn't have a valid SSL certificate, the user will be blocked from accessing it.

9. How do I test my headers before deploying to production? Use tools like curl -I https://staging.yoursite.com or inspect the Network tab in your browser's Developer Tools. You can also point SecHead's scanner at your staging environment.

10. What is Permissions-Policy and why do I need it? It allows you to explicitly deny browser features like the camera, microphone, and geolocation API. Even if you don't use these, disabling them prevents malicious third-party scripts (if injected) from hijacking them.

11. Why did my grade suddenly drop from A to B? Scanners regularly update their scoring criteria as new security standards emerge. For example, when Permissions-Policy became standard, many sites that previously scored an A dropped to a B until they implemented the new header.

12. Does a Grade B mean my site is insecure? Not necessarily. A Grade B means you are missing layers of defense-in-depth. Your site might still be perfectly secure at the application layer, but adding headers provides an essential safety net against inevitable bugs.

13. Is it possible to use Google Analytics with a strict CSP? Yes. You can whitelist Google Analytics by including https://www.google-analytics.com in your script-src and connect-src, or by utilizing a nonce for the inline GA initialization script.

14. What does X-Content-Type-Options: nosniff do? It prevents browsers from "MIME-sniffing" a response away from the declared content-type. This stops an attacker from uploading a disguised executable file (like a JS file disguised as an image) and forcing the browser to execute it.

15. How frequently should I scan my website's headers? It is highly recommended to integrate header scanning into your CI/CD pipeline using an API, or manually scan at least once a month and after every major infrastructure change.


Summary: Breaking out of a Security Headers Grade B plateau requires moving from basic checkbox compliance to implementing strict, thoughtful policies. By tackling your CSP, securing subdomains with HSTS, and restricting browser features via Permissions-Policy, you enhance your site's security posture and finally earn that A+.



Continue your journey into web security with these related, deep-dive articles from the SecHead team:

SEO Metadata

  • Meta Title: Why You Are Stuck at Security Headers Grade B (And How to Fix It)
  • Meta Description: Discover why your website is stuck at Security Headers Grade B. Learn how to fix CSP Grade issues, HSTS, and Permissions-Policy on Nginx, Apache, and Node.js.
  • URL Slug: why-stuck-at-grade-b
  • Primary Keyword: Security Headers Grade B
  • Secondary Keywords: Observatory, SecurityHeaders.com, CSP Grade

Free tool

Check your own security headers

Instant grade, plain-language explanations, and a full remediation plan - no signup needed.

Scan your site now →