← Blog  ·  October 2026

HTTP Security Headers Checker: Test and Fix Your Website's Security Configuration

What each missing header means, how to check them in 30 seconds, and how to fix the gaps before attackers find them first.

Most developers think about SQL injection, XSS, and authentication. Far fewer think about HTTP response headers — the silent layer that tells browsers how to behave when they load your site. A misconfigured or absent security header won't break your application. It will just leave the door open for clickjacking, cross-site scripting, sniffing attacks, and credential theft.

The good news: checking your headers takes 30 seconds. Fixing the most important ones takes minutes. This guide walks through each header that matters, how to test whether it's present, and what to do when it's missing.


What are HTTP security headers?

HTTP security headers are directives sent in the HTTP response from your server to the browser. They instruct the browser about what content to trust, which origins can load your resources, and how to handle protocol upgrades and permissions.

They don't protect your backend. They protect your users — specifically, they constrain what an attacker can do if they manage to inject malicious content into your pages or intercept traffic between your server and your users.

Six headers do the heavy lifting:


How to check your security headers with curl

The fastest way to inspect what your server is sending is a single curl command:

curl -I https://yourdomain.com

The -I flag fetches only the HTTP headers. You'll see output like:

HTTP/2 200
content-type: text/html; charset=utf-8
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin

Scan the list. Any security header that doesn't appear is missing. To filter specifically for the six headers above:

curl -sI https://yourdomain.com | grep -iE \
  "content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy"

If a header produces no output line, it's absent from your responses.

Test from outside your network CDNs, load balancers, and reverse proxies often inject or strip headers. Your localhost test may show headers that never reach production users, or miss headers added at the edge. Always test the public URL.

What each missing header means — and the risk

Missing header Risk Severity
Content-Security-Policy An attacker who injects inline script (e.g., via XSS) can execute arbitrary JavaScript in your users' browsers — stealing sessions, credentials, and data. High
Strict-Transport-Security Users accessing http:// can be intercepted before the redirect to HTTPS fires. Also leaves subdomains vulnerable to downgrade attacks if includeSubDomains is absent. High
X-Frame-Options Your page can be loaded inside an invisible iframe on a malicious site. A user clicking what appears to be a button on the attacker's site is actually clicking a button on yours — submitting forms, granting permissions, making purchases. Medium–High
X-Content-Type-Options Older browsers may interpret a response as executable even when your server declares it as plain text. A user-uploaded file served from your domain could run as JavaScript. Medium
Referrer-Policy Full URLs — including path, query parameters, and tokens — are sent as the Referer header to external sites your pages link to. Password reset tokens and session identifiers embedded in URLs leak to third parties. Medium
Permissions-Policy Embedded iframes and injected scripts can silently access camera, microphone, geolocation, and payment APIs without user re-consent, depending on browser defaults. Low–Medium

How to fix each missing header

Content-Security-Policy

CSP is the most complex header to configure correctly, but you don't need a perfect policy immediately. Start with a report-only policy to observe what would be blocked, then tighten it:

# Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';" always;

# Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';"

The frame-ancestors 'none' directive in CSP supersedes X-Frame-Options in modern browsers — set both for backward compatibility.

Strict-Transport-Security

# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Start with max-age=300 (5 minutes) when first deploying. Once you've confirmed HTTPS works correctly across all subdomains, raise it to 31536000 (1 year) and consider HSTS preload submission.

X-Frame-Options

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

# Apache
Header always set X-Frame-Options "SAMEORIGIN"

Use DENY if your site should never be framed at all. Use SAMEORIGIN if you embed your own pages in iframes on the same origin.

X-Content-Type-Options

This one has no configuration — it's always the same value:

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

# Apache
Header always set X-Content-Type-Options "nosniff"

Referrer-Policy

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

# Apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"

strict-origin-when-cross-origin is the current browser default and a safe choice for most sites. It sends the full URL for same-origin requests and only the origin (no path or query) for cross-origin requests.

Permissions-Policy

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

# Apache
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"

An empty parentheses value (()) disables the feature entirely. List only the APIs you actually use as self and deny everything else.

Use the always flag in Nginx Without always, Nginx only sends add_header directives on 2xx and 3xx responses. Error pages (400, 404, 500) — which are frequently targeted by injection attempts — will miss the headers entirely.

Verifying your changes

After deploying a header change, confirm it's live with curl from outside your network:

curl -sI https://yourdomain.com | grep -i "content-security-policy"

If you're behind a CDN, purge the cache first — stale cached responses won't carry new headers. For Cloudflare, the Development Mode toggle or a Cache Purge from the dashboard forces fresh responses immediately.

Also verify your error pages carry the same headers:

curl -sI https://yourdomain.com/404-page-that-does-not-exist | grep -iE "x-frame-options|x-content-type"

Headers change over time

Infrastructure changes — CDN config edits, reverse proxy updates, framework upgrades, new deploy pipelines — silently modify headers. A header that was correctly set last quarter may have been stripped or weakened by a configuration drift you never noticed.

Security headers aren't a one-time checkbox. They need ongoing monitoring the same way SSL certificates do.

🔍 Free HTTP Security Headers Checker

Instantly see which security headers your site is sending and which are missing — no signup, no install. Paste your URL and get a full report in seconds.

Check your headers now →

If you want to know immediately when a header gets dropped or weakened — without running curl on a schedule yourself — HeaderGuard monitors your site's security headers continuously and alerts you the moment something changes.

🛡️ HeaderGuard — Security Header Monitoring

Monitor your site's HTTP security headers for changes. Get alerted the moment a header is dropped, weakened, or misconfigured — before attackers notice it first.

Join the waitlist → Free instant check →