TL;DR
HTTP security headers are your first line of defense against cross-site scripting (XSS), clickjacking,
MIME sniffing, and data injection attacks. Despite being simple response headers, a 2024 scan of the
top 1 million websites found that fewer than 12% deploy a Content Security Policy. This guide
covers every critical security header with production-ready Nginx and Apache configurations.
📑 Table of Contents
- Why Security Headers Matter
- Content Security Policy (CSP)
- Strict-Transport-Security (HSTS)
- X-Frame-Options
- X-Content-Type-Options
- Referrer-Policy
- Permissions-Policy
- Additional Useful Headers
- Nginx & Apache Configuration
- Best Practices
- Common Mistakes
- Tools
- References
Why Security Headers Matter
Security headers instruct browsers on how to handle your content — which scripts can run,
whether your page can be framed, and what information is leaked in referrers. They cost nothing to deploy
and defend against entire categories of attacks identified in the OWASP Top 10.
📖 Definition — HTTP security headers are response headers sent by the server that activate browser-side security mechanisms, restricting behavior that could be exploited by attackers.
Content Security Policy (CSP)
CSP is the most powerful security header. It defines an allowlist of content sources, effectively neutralizing
XSS, data injection, and unauthorized inline scripts.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
| Directive | Controls | Recommended Value |
|---|---|---|
default-src |
Fallback for all resource types | 'self' |
script-src |
JavaScript sources |
'self' + specific CDNs |
style-src |
CSS sources |
'self' (avoid 'unsafe-inline') |
img-src |
Image sources | 'self' data: https: |
frame-ancestors |
Who can embed your page | 'none' |
base-uri |
Restricts `` element | 'self' |
🎯 Start with Content-Security-Policy-Report-Only to log violations without blocking. Use the report-uri or report-to directive to collect reports, then tighten the policy iteratively.
🚫 Never use 'unsafe-eval' in production CSP. It re-enables eval(), completely undermining XSS protection. Refactor code that calls eval(), new Function(), or inline event handlers.
Strict-Transport-Security (HSTS)
Forces browsers to connect over HTTPS only, preventing protocol downgrade attacks and SSL stripping.
`http
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
`
⚠️ Once HSTS is deployed with includeSubDomains, every subdomain must have a valid TLS certificate. Rolling this out without full HTTPS coverage will break subdomains.
X-Frame-Options
Prevents your page from being embedded in , , or
`` elements on other sites — blocking clickjacking attacks.
X-Frame-Options: DENY
| Value | Behavior |
|---|---|
| DENY | Never allow framing (most secure) |
| SAMEORIGIN | Allow framing only from same origin |
💡 CSP's frame-ancestors directive is the modern replacement for X-Frame-Options, offering more granular control. Deploy both for backward compatibility.
X-Content-Type-Options
Prevents browsers from MIME-sniffing a response away from the declared Content-Type.
Blocks attacks that disguise executable content as harmless file types.
X-Content-Type-Options: nosniff
Referrer-Policy
Controls how much referrer information is sent when navigating away from your site.
Referrer-Policy: strict-origin-when-cross-origin
| Policy | Same-Origin | Cross-Origin (HTTPS→HTTPS) | Downgrade (HTTPS→HTTP) |
|---|---|---|---|
no-referrer |
None | None | None |
strict-origin |
Full URL | Origin only | None |
strict-origin-when-cross-origin |
Full URL | Origin only | None |
no-referrer-when-downgrade |
Full URL | Full URL | None |
Permissions-Policy
Controls which browser features (camera, microphone, geolocation, etc.) your site and embedded iframes can use.
Formerly known as Feature-Policy.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self), usb=()
⚡ Pro Tip: Set unused features to () (empty allowlist) to explicitly disable them. This prevents embedded third-party scripts from silently accessing sensitive APIs like the camera or microphone.
Additional Useful Headers
| Header | Value | Purpose |
|---|---|---|
Cross-Origin-Opener-Policy |
same-origin |
Isolates browsing context, enables SharedArrayBuffer |
Cross-Origin-Embedder-Policy |
require-corp |
Ensures all embedded resources opt-in to being loaded |
Cross-Origin-Resource-Policy |
same-origin |
Prevents other origins from loading your resources |
X-DNS-Prefetch-Control |
off |
Prevents speculative DNS lookups (privacy) |
Nginx & Apache Configuration
Nginx
# /etc/nginx/snippets/security-headers.conf
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
# Include in server block:
# include snippets/security-headers.conf;
Apache
# .htaccess or httpd.conf
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; frame-ancestors 'none';"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options "DENY"
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=()"
Best Practices
Deploy CSP in report-only mode first, analyze violations, then enforce.
Use nonce-based CSP ('nonce-{random}') instead of 'unsafe-inline' for inline scripts.
Add the always keyword in Nginx to send headers on all response codes (including 4xx/5xx).
Test headers in staging before production — overly strict CSP can break legitimate functionality.
Audit headers regularly with automated scanners as your site's dependencies evolve.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
Using 'unsafe-inline' + 'unsafe-eval' in CSP |
Nullifies XSS protection | Use nonces or hashes instead |
Missing always keyword in Nginx |
Headers absent on error pages | Add always to every add_header
|
| HSTS without full HTTPS coverage | Subdomains become unreachable | Ensure all subdomains have valid TLS certs first |
Forgetting frame-ancestors in CSP |
Clickjacking still possible | Add frame-ancestors 'none' to CSP |
Setting Referrer-Policy: unsafe-url
|
Full URL leaked to third parties | Use strict-origin-when-cross-origin
|
Tools
Scan your website's security headers:
- 🔧 Security Header Scanner — Analyze all security headers and get an actionable report with grades.
References
📄 MDN — Content Security Policy (CSP)
📄 MDN — Strict-Transport-Security
📄 MDN — Permissions-Policy
📄 OWASP Secure Headers Project
📄 OWASP HTTP Headers Cheat Sheet
📄 MDN — Referrer-Policy
🎯 Key Takeaway: Security headers are free, high-impact defenses. At minimum, deploy CSP, HSTS, X-Frame-Options,
X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. Start CSP in report-only mode,
iterate based on real violation reports, then enforce. Combine with a regular scanning cadence
to catch regressions as third-party dependencies change.
Originally published on StarNomina ToolBox. Try our free online tools — no signup required.
Top comments (0)