Skip to main content

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:

ParameterDescription
max-age=63072000Duration the browser enforces HTTPS (seconds, ~2 years)
includeSubDomainsApply to all subdomains
preloadEligible 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:

DirectiveDescription
default-src 'self'Default for all resources: same origin only
script-srcAllowed JavaScript origins
style-srcAllowed CSS origins
img-srcAllowed 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.