Security Headers: HSTS, CSP, OCSP Stapling
HTTPS alone is not enough. Adding security headers blocks a wide range of threats including XSS, clickjacking, and protocol downgrade attacks.
HSTS (HTTP Strict Transport Security)β
Forces the browser to always connect to this domain over HTTPS. If the user tries to access via HTTP, the browser switches to HTTPS without even sending a request to the server.
# Nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Apache
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Parameters:
| Parameter | Description |
|---|---|
max-age=63072000 | Duration the browser enforces HTTPS (seconds, ~2 years) |
includeSubDomains | Apply to all subdomains |
preload | Eligible for HSTS Preload list submission |
Caution: Once HSTS is set, users cannot access the domain if only HTTP is available for the duration of max-age. Test with max-age=300 (5 minutes) first, then increase.
HSTS Preloadβ
Register at hstspreload.org to have Chrome, Firefox, and other browsers enforce HTTPS from the very first connection.
X-Frame-Options (Clickjacking Protection)β
Prevents your site from being embedded in another domain's iframe.
add_header X-Frame-Options "SAMEORIGIN" always;
# DENY : never allow embedding
# SAMEORIGIN : allow embedding on the same domain only (recommended)
# ALLOW-FROM uri : allow only from a specific URI (legacy, not recommended)
X-Content-Type-Options (MIME Sniffing Protection)β
Prevents the browser from ignoring the server-specified Content-Type and guessing the content type (MIME sniffing).
add_header X-Content-Type-Options "nosniff" always;
Content-Security-Policy (CSP)β
The most powerful header for defending against XSS (Cross-Site Scripting). It tells the browser which sources are allowed to load resources.
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://fonts.googleapis.com;
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';
" always;
Key directives:
| Directive | Description |
|---|---|
default-src 'self' | Default for all resources: same origin only |
script-src | Allowed JavaScript origins |
style-src | Allowed CSS origins |
img-src | Allowed image origins |
frame-ancestors 'none' | Block iframe embedding entirely (replaces X-Frame-Options) |
'unsafe-inline' | Allow inline scripts (avoid this) |
'nonce-{value}' | Allow only a specific script (recommended) |
CSP Report-Only (test mode):
# Violations are reported but not blocked (use when first introducing CSP)
add_header Content-Security-Policy-Report-Only "
default-src 'self';
report-uri /csp-report;
" always;
Referrer-Policyβ
Controls what URL information is sent in the Referer header when navigating to other sites.
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# no-referrer : never send Referer header
# same-origin : full URL within same domain only
# strict-origin-when-cross-origin: domain only for cross-origin (recommended)
# unsafe-url : always send full URL (not recommended)
Permissions-Policy (Feature Policy)β
Controls access to browser features such as camera, microphone, and geolocation.
add_header Permissions-Policy "
geolocation=(),
microphone=(),
camera=(),
payment=(self),
usb=()
" always;
OCSP Staplingβ
Reduces client latency by having the server pre-fetch the OCSP response that proves the certificate has not been revoked.
# Nginx
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Apache (global config)
SSLUseStapling On
SSLStaplingCache shmcb:/run/apache2/ssl_stapling(32768)
SSLStaplingReturnResponderErrors off
SSLStaplingStapleMaxAge 3600
Complete Security Headers Nginx Configurationβ
# /etc/nginx/snippets/security-headers.conf
# Reuse across server blocks with include
# HSTS (2 years)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Clickjacking protection
add_header X-Frame-Options "SAMEORIGIN" always;
# MIME sniffing protection
add_header X-Content-Type-Options "nosniff" always;
# XSS filter (legacy browsers)
add_header X-XSS-Protection "1; mode=block" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions policy
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# CSP (adjust to match your service)
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com;" always;
# Hide server info
server_tokens off;
# Use include in each server block
server {
listen 443 ssl;
server_name example.com;
include /etc/nginx/snippets/security-headers.conf;
# ...
}
Tools for Checking Security Headersβ
# Check headers with curl
curl -I https://example.com
# Check score via securityheaders.com API
curl "https://securityheaders.com/?q=https://example.com&followRedirects=on" \
-o /dev/null -w "%{http_code}\n"
# Parse specific headers locally
curl -sI https://example.com | grep -iE "strict-transport|x-frame|content-security|x-content"
Visit securityheaders.com to check your A+ grade goal.
The next page covers advanced TLS settings β TLS 1.3-only configuration and disabling weak ciphers.