Security headers: HSTS, CSP, X-Frame-Options and others
· 7 min read
In brief: Security HTTP headers are lines the server adds to every HTTP response to tell the browser how to handle your site. Correctly set, they eliminate whole classes of attacks (XSS, clickjacking, MITM) - and they're free.
In brief: Security HTTP headers are lines the server adds to every HTTP response to tell the browser how to handle your site. Correctly set, they eliminate whole classes of attacks (XSS, clickjacking, MITM) - and they're free.
Why they matter
An application can be perfectly backend-side secure, but without proper headers still vulnerable to browser-side attacks: cross-site scripting (XSS), clickjacking, protocol downgrade, MIME confusion. Security headers push defense into the browser.
Strict-Transport-Security (HSTS)
Forces HTTPS for all future visits. After the first HTTPS visit the browser remembers the domain and itself rewrites any http:// link to https://, even if the user clicks a bad answer.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age=63072000= valid for 2 yearsincludeSubDomains= the rule applies to all subdomains too (caveat: if you have an HTTP-only subdomain, it breaks it)preload= allows inclusion in the HSTS preload list built into Chrome / Firefox / Safari
Caution:
HSTS with preload takes 6+ months to remove from the list. Don't include preload before you verify HTTPS works stably for all subdomains.
Content-Security-Policy (CSP)
The most powerful but also hardest header to configure. Whitelists where the browser may load scripts, styles, images, iframes from. Without CSP an attacker who can inject a <script> tag can run arbitrary JS - with CSP only code from approved sources.
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-...'; img-src 'self' data: https://cdn.example.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'
Common directives:
default-src 'self'= by default allow only resources from my domainscript-src= JS sources (specify hash or nonce for inline scripts)style-src= CSS sourcesimg-src= imagesconnect-src= AJAX, WebSocket, EventSourceframe-ancestors 'none'= nobody may embed the page in an iframe (better than X-Frame-Options)report-uri /csp-report= browser sends JSON for every violation (you catch it in the backend and log)
X-Frame-Options
Older alternative to frame-ancestors. Prevents clickjacking - attacker embeds your page as an iframe and overlays invisible buttons.
X-Frame-Options: DENY
Values: DENY (nobody), SAMEORIGIN (only my domain), ALLOW-FROM uri (deprecated).
X-Content-Type-Options
X-Content-Type-Options: nosniff
Disables MIME sniffing - the browser will respect the Content-Type the server returned. Without it an attacker can upload a file with the wrong type (e.g. an image that's actually HTML with script) and the browser may run it as a web page.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Controls how much information about the previous page the browser sends in the Referer header on click. Default in modern browsers is already strict-origin-when-cross-origin, but the explicit declarative header ensures consistency.
Permissions-Policy
Disables APIs you don't need - camera, microphone, GPS, geolocation. An attacker via XSS cannot request these APIs.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Practical nginx configuration
server {
listen 443 ssl http2;
server_name example.com;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP is extensive - define according to your application
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; ..." always;
}
The key is always - without it nginx will omit headers on error responses (4xx, 5xx).
Auditing the current state
Easiest via an online tool. Our header check will show you which headers are missing and which are wrong. For a thorough audit securityheaders.com also gives an A-F score.
Conclusion
Security headers are one of the best investments in effort-to-payoff ratio in security - typically half an hour of setup in reverse proxy against a whole category of browser-side attacks. An audit once every six months as part of regular maintenance is a reasonable minimum.
Free security headers audit
No registration, result in three seconds.
Try ePulz.io free - 7 days, no credit card needed.
Create account