HTTP security headers are instructions from a web server to the browser that control how the browser handles the site's content. They prevent cross-site scripting, clickjacking, MIME type confusion, information leakage, and unauthorized framing. They cost nothing to implement, require no code changes, and can be deployed in minutes.
Despite this, the majority of websites fail basic security header audits. A scan of the Alexa top 1 million sites consistently shows that fewer than 10% implement a Content-Security-Policy, and most omit at least three critical headers. The result is a grade of F -- the default state for any website that has not explicitly configured security headers.
The Critical Security Headers
1. Content-Security-Policy (CSP)
Prevents: Cross-site scripting (XSS), data injection, and clickjacking. CSP is the single most important security header. It defines exactly which sources of content (scripts, styles, images, fonts, frames) the browser is allowed to load. Any content not explicitly allowed is blocked.
Without CSP, a single XSS vulnerability allows an attacker to inject arbitrary JavaScript that runs with the full permissions of your site -- stealing cookies, redirecting users, exfiltrating data.
# nginx — strict CSP
add_header Content-Security-Policy
"default-src 'self';
script-src 'self' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';"Start with Content-Security-Policy-Report-Only to monitor violations without breaking functionality. Once you have reviewed the violation reports and adjusted the policy, switch to enforcement mode.
2. Strict-Transport-Security (HSTS)
Prevents: Protocol downgrade attacks, cookie hijacking, SSL stripping. HSTS tells browsers to only connect via HTTPS -- even if the user types http://. Without HSTS, the first request to your site is vulnerable to man-in-the-middle attacks that strip the TLS connection.
# nginx — HSTS with preload
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload" always;The max-age value is in seconds. 63072000 = 2 years. The includeSubDomains directive applies HSTS to all subdomains. The preload directive allows submission to the browser preload list, which hardcodes HSTS in browsers -- eliminating even the first insecure request.
Before enabling includeSubDomains, verify that ALL subdomains support HTTPS. A subdomain without a valid TLS certificate will become completely inaccessible once HSTS is enforced.
3. X-Content-Type-Options
Prevents: MIME type sniffing. Without this header, browsers may "guess" the content type of a response -- interpreting a text file as JavaScript, for example. This enables attacks where an attacker uploads a file with a harmless extension that the browser executes as code.
# nginx
add_header X-Content-Type-Options "nosniff" always;This is the simplest security header to implement. One line, no configuration needed, no risk of breaking anything. There is no reason not to deploy it.
4. X-Frame-Options
Prevents: Clickjacking. This header controls whether your site can be embedded in an <iframe> on another site. Clickjacking attacks overlay a transparent iframe of your site over a malicious page, tricking users into clicking buttons they cannot see.
# nginx — prevent all framing
add_header X-Frame-Options "DENY" always;
# OR allow same-origin framing only
add_header X-Frame-Options "SAMEORIGIN" always;Note: CSP's frame-ancestors directive is the modern replacement for X-Frame-Options and provides more granular control. Use both for backward compatibility.
5. Referrer-Policy
Prevents: Information leakage through the Referer header. When a user clicks a link from your site to another site, the browser sends a Referer header containing the full URL of the originating page. This can leak sensitive information -- internal URL structures, query parameters, user IDs embedded in URLs.
# nginx — send origin only for cross-origin requests
add_header Referrer-Policy "strict-origin-when-cross-origin" always;The strict-origin-when-cross-origin policy sends the full URL for same-origin requests (useful for your own analytics) but only the origin (domain) for cross-origin requests. This balances functionality with privacy.
6. Permissions-Policy
Prevents: Unauthorized access to browser APIs. Permissions-Policy (formerly Feature-Policy) controls which browser features your site can use: camera, microphone, geolocation, payment, USB, and more. By explicitly disabling features you do not use, you reduce the attack surface available if your site is compromised.
# nginx — restrict browser APIs
add_header Permissions-Policy
"camera=(), microphone=(), geolocation=(),
payment=(), usb=(), magnetometer=(),
gyroscope=(), accelerometer=()" always;7. Cross-Origin Headers (COOP, CORP, COEP)
These three headers work together to enable cross-origin isolation, which is required for advanced browser features like SharedArrayBuffer and high-resolution timers.
- Cross-Origin-Opener-Policy (COOP):
same-origin-- prevents other sites from opening a reference to your window - Cross-Origin-Resource-Policy (CORP):
same-origin-- prevents other sites from loading your resources - Cross-Origin-Embedder-Policy (COEP):
require-corp-- ensures all resources loaded by your page have explicit permission
# nginx — full cross-origin isolation
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;The Grading System
Security header grading follows a methodology similar to the Mozilla Observatory. Each header is worth points based on its importance and configuration quality.
| Grade | Criteria |
|---|---|
| A | All critical headers present and correctly configured, including CSP |
| B | Most headers present, CSP may be in report-only mode |
| C | Basic headers present (HSTS, X-Content-Type-Options) but missing CSP |
| D | Some headers present but with weak configurations |
| F | Missing critical headers or no security headers at all |
Most websites score F because security headers are not enabled by default in any web server or framework. You have to actively configure them. The good news: going from F to B takes about 10 minutes of nginx configuration.
Complete nginx Configuration
Here is a production-ready security header configuration that achieves a A grade. Add this to your server block:
# Security Headers — A Grade Configuration
# XSS and injection protection
add_header Content-Security-Policy
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
img-src 'self' data: https:; font-src 'self'; connect-src 'self';
frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
# Force HTTPS
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload" always;
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# Prevent clickjacking
add_header X-Frame-Options "DENY" always;
# Control referrer information
add_header Referrer-Policy
"strict-origin-when-cross-origin" always;
# Restrict browser APIs
add_header Permissions-Policy
"camera=(), microphone=(), geolocation=(), payment=()" always;
# Cross-origin isolation
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;Testing Your Headers
After deploying security headers, verify them:
# Check all response headers
curl -sI https://example.com | grep -iE "content-security|strict-transport|x-content|x-frame|referrer|permissions|cross-origin"Automated tools like Mozilla Observatory (observatory.mozilla.org) provide a detailed grade and recommendations. MAGO's domain intelligence reports include security header analysis as part of the standard assessment, grading each header individually and providing specific remediation guidance.
Common Mistakes
- Setting CSP too permissively: A policy of
default-src *is the same as having no CSP at all. The value comes from restriction, not presence. - Using
unsafe-inlineandunsafe-evalin CSP: These directives disable CSP's XSS protection for scripts. Use nonces or hashes instead. - Forgetting the
alwaysdirective in nginx: Withoutalways, headers are only sent for 200/204/301/302/304 responses. Error pages (404, 500) will not have security headers. - Not testing with all page types: Security headers on your homepage mean nothing if your API endpoints, error pages, and redirects do not have them.
- Deploying HSTS with includeSubDomains before auditing all subdomains: Any subdomain without HTTPS will become inaccessible. Audit your subdomain inventory first.
OWASP Secure Headers Project -- comprehensive reference for all security headers and recommended values. Mozilla Observatory -- free header audit tool with detailed grading methodology. MDN Web Docs -- authoritative documentation for each header's syntax and browser support.