Why Security Headers Matter
Security headers are HTTP response headers that instruct browsers to enable security features. They're your first line of defense against common web attacks like:
- Cross-Site Scripting (XSS) — Injecting malicious scripts
- Clickjacking — Tricking users into clicking hidden elements
- MIME Sniffing — Browsers misinterpreting content types
- Protocol Downgrade — Forcing HTTPS connections to HTTP
- Information Leakage — Revealing sensitive data via referrer headers
Essential Security Headers
| Header | Purpose | Priority |
|---|---|---|
Strict-Transport-Security |
Force HTTPS connections | Critical |
Content-Security-Policy |
Prevent XSS and injection attacks | Critical |
X-Frame-Options |
Prevent clickjacking | High |
X-Content-Type-Options |
Prevent MIME sniffing | High |
Referrer-Policy |
Control referrer information | Medium |
Permissions-Policy |
Control browser features | Medium |
Strict-Transport-Security (HSTS)
HSTS tells browsers to only access your site via HTTPS, preventing protocol downgrade attacks and cookie hijacking.
# Recommended HSTS configuration
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Parameters Explained
max-age=31536000— Remember for 1 year (in seconds)includeSubDomains— Apply to all subdomainspreload— Allow inclusion in browser preload listsalways— Send header even on error responses
HSTS Preload Warning
Only use preload if you're certain all current and future subdomains will support HTTPS. Removing a domain from preload lists requires months.
Content-Security-Policy (CSP)
CSP is the most powerful header for preventing XSS. It defines which sources of content are allowed to load.
# Basic CSP - adjust based on your needs
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
Key Directives
default-src— Fallback for other directivesscript-src— Allowed JavaScript sourcesstyle-src— Allowed CSS sourcesimg-src— Allowed image sourcesconnect-src— Allowed fetch/XHR/WebSocket targetsframe-ancestors— Who can embed your page (replaces X-Frame-Options)
Content-Security-Policy-Report-Only first to test your policy without breaking functionality. Monitor the reports before enforcing.
X-Frame-Options
Prevents your site from being embedded in iframes, protecting against clickjacking attacks.
# Prevent all framing
add_header X-Frame-Options "DENY" always;
# Or allow same-origin framing
add_header X-Frame-Options "SAMEORIGIN" always;
Options
DENY— Never allow framingSAMEORIGIN— Only allow framing from same origin
Note: frame-ancestors in CSP is more flexible and should be preferred for modern browsers.
X-Content-Type-Options
Prevents browsers from MIME-sniffing responses, which can turn non-executable content into executable code.
add_header X-Content-Type-Options "nosniff" always;
Referrer-Policy
Controls how much referrer information is sent when navigating to other sites.
# Recommended: send origin only for cross-origin requests
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Common Values
no-referrer— Never send referrersame-origin— Only send referrer for same-origin requestsstrict-origin-when-cross-origin— Full URL for same-origin, only origin for cross-origin (recommended)
Permissions-Policy
Controls which browser features (camera, microphone, geolocation, etc.) can be used.
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;
The add_header Inheritance Problem
NGINX's add_header directive has a critical gotcha: when you use add_header in a nested block (like a location), it replaces all headers from parent blocks.
# WRONG: location block drops server-level headers!
server {
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
location /api/ {
add_header X-Custom "value" always;
# X-Frame-Options and X-Content-Type-Options are GONE!
}
}
# RIGHT: repeat all headers in nested blocks
server {
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
location /api/ {
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Custom "value" always;
}
}
add_header_inherit on; in nested blocks to automatically inherit parent headers.
location /api/ {
add_header_inherit on;
add_header X-Custom "value" always;
}
Complete Recommended Configuration
# Place in http block or server block
# Security headers for all responses
# HSTS - force HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Prevent clickjacking
add_header X-Frame-Options "DENY" always;
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# Control referrer
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Restrict browser features
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;
# CSP - customize based on your application
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'none';" always;
Detecting Issues with Gixy
Gixy detects several header-related misconfigurations:
- Missing HSTS — HTTPS servers without Strict-Transport-Security
- Weak HSTS max-age — Values less than 6 months
- Header redefinition — Nested blocks dropping parent headers
$ gixy /etc/nginx/nginx.conf
[hsts_header] Missing HSTS header
Severity: MEDIUM
Reason: No Strict-Transport-Security header found.
[add_header_redefinition] Nested "add_header" drops parent headers.
Severity: MEDIUM
Reason: Parent header(s) "x-frame-options" dropped in nested block
Testing Your Headers
After configuration, verify your headers:
# Check response headers
curl -I https://yourdomain.com
# Or use online tools:
# - securityheaders.com
# - observatory.mozilla.org