Why HTTP Security Headers Matter
HTTP security headers are response headers that instruct the browser how to behave when handling your site's content. They are one of the highest-value, lowest-effort security controls available β most take under five minutes to implement and protect against entire vulnerability classes including XSS, clickjacking, protocol downgrade attacks, and MIME-type confusion.
Despite this, they are consistently absent. A scan of the Alexa top 1 million sites finds that over 95% are missing at least one critical security header. Scanner tools like securityheaders.com give most production applications a grade of D or F.
Where to set them: Security headers can be set in your web server config (nginx, Apache), CDN (Cloudflare, Fastly), load balancer, or application framework middleware. The framework approach is easiest to version-control and review.
Content-Security-Policy (CSP)
CSP is the most powerful β and most complex β security header. It defines which sources of content (scripts, styles, images, fonts, frames) the browser is permitted to load. A properly configured CSP eliminates the impact of most XSS attacks by preventing injected scripts from executing.
# Strict CSP β adjust sources to match your actual CDN/fonts add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{RANDOM}'; style-src 'self' 'nonce-{RANDOM}' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data: cdn.yoursite.com; connect-src 'self' api.yoursite.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests";
Start with report-only mode
CSP is notoriously easy to break in production. Start with Content-Security-Policy-Report-Only and a report-uri endpoint. Run it for two weeks, fix all the legitimate violations, then switch to enforcement mode.
Never use unsafe-inline or unsafe-eval: These directives defeat the entire purpose of CSP. If your codebase relies on inline scripts, use nonces β a per-request cryptographic token injected into each allowed script tag.
Strict-Transport-Security (HSTS)
HSTS tells browsers to only ever connect to your domain over HTTPS β even if the user types http:// or follows an HTTP link. Once a browser has seen an HSTS header, it will internally redirect to HTTPS before making any network request for the specified duration.
# Two-year max-age, include subdomains, opt into preload list add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
The preload directive allows your domain to be added to Chrome's HSTS preload list β a hardcoded list of domains that Chrome connects to over HTTPS before ever seeing the header. Submit at hstspreload.org.
Important: Only add preload if every subdomain of your domain supports HTTPS. HSTS with includeSubDomains; preload on a domain that has HTTP-only subdomains will break those subdomains for all Chrome users β and it's very difficult to reverse.
X-Frame-Options
X-Frame-Options prevents your pages from being embedded in iframes on other sites, mitigating clickjacking attacks. Clickjacking places an invisible iframe of your site over a deceptive UI to trick users into clicking on your buttons.
add_header X-Frame-Options "DENY" always; # Or SAMEORIGIN if you need to iframe your own pages: # add_header X-Frame-Options "SAMEORIGIN" always;
CSP supersedes X-Frame-Options: The frame-ancestors CSP directive is more flexible and overrides X-Frame-Options in modern browsers. Set both for maximum compatibility with older browsers.
X-Content-Type-Options
Without this header, browsers perform MIME-type sniffing β inferring the content type from the content itself rather than the Content-Type header. An attacker can upload a file that looks like an image but contains JavaScript, and the browser might execute it.
add_header X-Content-Type-Options "nosniff" always;This is the simplest header to set and has essentially zero risk of breaking anything. There is no legitimate reason not to have it.
Referrer-Policy
When a user clicks a link from your site to an external site, the browser sends a Referer header containing the full URL of the page they were on. This can leak sensitive path information β user IDs, session tokens in URLs, or internal path structure.
# Send origin only for same-origin, nothing for cross-origin add_header Referrer-Policy "strict-origin-when-cross-origin" always;
no-referrerβ never send the Referer header (breaks some analytics)strict-origin-when-cross-originβ send full URL same-origin, just the origin cross-origin (recommended)same-originβ only send Referer for same-origin requests
Permissions-Policy
Formerly Feature-Policy, this header controls which browser features and APIs your page (and embedded iframes) can use β camera, microphone, geolocation, payment, and more. Restricting unused APIs reduces the attack surface for malicious third-party scripts.
# Disable everything you don't use add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=(), interest-cohort=()" always;
Remove the Server Header
By default, web servers advertise their name and version in the Server header (Server: nginx/1.18.0 or Server: Apache/2.4.51). This helps attackers identify unpatched software versions instantly.
server_tokens off; # nginx: removes version from Server header # Apache: ServerTokens Prod + ServerSignature Off
Also remove or sanitize X-Powered-By (often set by frameworks to e.g. PHP/8.1.2). In Express.js: app.disable('x-powered-by').
Testing Your Security Headers
After deployment, verify your header configuration:
# Check headers with curl curl -I https://yoursite.com # Check specific header curl -s -I https://yoursite.com | grep -i "content-security-policy" # Full header audit β securityheaders.com API curl "https://securityheaders.com/?q=yoursite.com&followRedirects=on"
- securityheaders.com β grades your header configuration, explains each finding
- Mozilla Observatory β comprehensive header and TLS analysis
- OWASP ZAP β includes passive header checks in every scan
- AquilaX DAST β checks headers as part of automated security scanning in CI/CD
Security Headers Checklist
Content-Security-Policyβ deployed in report-only, then enforcement; nounsafe-inlineStrict-Transport-Securityβ max-age β₯ 1 year, includeSubDomains if safeX-Frame-Options: DENY(or SAMEORIGIN) β plusframe-ancestorsin CSPX-Content-Type-Options: nosniffβ zero risk, always setReferrer-Policy: strict-origin-when-cross-originβ stops URL leakagePermissions-Policyβ disable camera, mic, geolocation, payment if not used- Remove or sanitize
ServerandX-Powered-Byheaders - Verify with securityheaders.com and Mozilla Observatory after each deployment
Scan Your App for Missing Security Headers
AquilaX DAST checks your deployed application for every missing or misconfigured security header and explains how to fix each one.
Start Free Scan