Nginx Security Headers Config: Comprehensive Site Hardening
Nginx HTTP security response headers: CSP, HSTS, X-Frame-Options and 7 core policies. Full config plus online checks. One-stop HTTPS hardening.
Scope
For Nginx web-server admins and security ops. Blog, corporate site, or shop: HTTP security headers (security response headers) cut XSS, clickjacking, MIME sniffing, and MITM. Apply on reverse proxy, load balancer, or standalone Nginx.
Prerequisites
- root or sudo on the Nginx host
- Nginx installed (1.10+; 1.18+ preferred)
- Know nginx.conf and site conf layout
- Stage first; then production
How it works
HTTP security headers are response directives. The browser tightens policy on receipt.
- Content-Security-Policy (CSP): allowlist. Load scripts, styles, fonts only from trusted origins. Stops XSS at the source.
- X-Frame-Options: forbid embedding in <frame> or <iframe>. Stops clickjacking.
- X-Content-Type-Options: nosniff: honor declared Content-Type. Stops MIME confusion.
- Strict-Transport-Security (HSTS): HTTPS only. Stops SSL stripping MITM.
- Referrer-Policy: cap Referer content. Cuts privacy leak and path disclosure.
- Permissions-Policy: lock camera, microphone, geolocation, and other APIs. Shrink attack surface.
Steps
Step 1: Locate Nginx config
Confirm config paths:
`# Locate the main configuration file
sudo nginx -t 2>&1 | grep "test failed" || sudo nginx -t 2>&1
# Typical paths
ls /etc/nginx/nginx.conf
ls /etc/nginx/conf.d/
ls /etc/nginx/sites-available/`
Step 2: Add security headers in the server block
Edit the site conf (e.g. /etc/nginx/sites-available/site.conf). Inside server, outside location:
`server {
listen 443 ssl http2;
server_name helixcyber.ai;
# ===== HTTP security headers config start =====
# HSTS: force HTTPS, 1-year max-age, include subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Forbid embedding in iframe (clickjacking defense)
add_header X-Frame-Options "SAMEORIGIN" always;
# Disable MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Enable XSS filter (deprecated in modern browsers; retained for legacy compatibility)
add_header X-XSS-Protection "1; mode=block" always;
# Content-Security-Policy: same-origin resources + trusted CDN only
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-ancestors 'self';
form-action 'self';
" always;
# Control Referer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions-Policy: disable non-essential APIs
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
# ===== Security headers config end =====
# Existing config continues...
location / {
proxy_pass http://127.0.0.1:8080;
# ...
}
}`
Step 3: Syntax-check and reload Nginx
`# Check syntax
sudo nginx -t
# Reload if syntax is valid
sudo systemctl reload nginx
# or
sudo nginx -s reload`
Step 4: Verify headers with curl
`# Inspect response headers
curl -sI https://helixcyber.ai | grep -iE "^(strict-transport|content-security|x-frame|x-content|x-xss|referrer|permissions-policy)"
# Expected example:
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# content-security-policy: default-src 'self'; script-src 'self' ...
# x-frame-options: SAMEORIGIN
# x-content-type-options: nosniff
# referrer-policy: strict-origin-when-cross-origin`
Step 5: Online scan
Scan the domain with Mozilla Observatory (https://observatory.mozilla.org/). Score plus remediations. A+ is best.
Verification
After the steps, check:
- nginx -t reports
syntax is okandtest is successful - DevTools Network tab shows every configured security header
- Site still works: render, form POST, third-party analytics (e.g. Google Analytics) not blocked by CSP
- Mozilla Observatory score B or better; target A
FAQ
Q1: Styles or JS break after CSP?
Policy too tight. F12 → Console. Read CSP violations. Whitelist the origin on the matching directive. Google Analytics needs script-src 'self' https://www.googletagmanager.com. Use Content-Security-Policy-Report-Only first; enforce after clean.
Q2: HTTP dead after HSTS?
HSTS forces HTTPS. Keep HTTP→HTTPS 301: (1) listen on 80; (2) HTTP server block has return 301 https://$host$request_uri;. Rollback: chrome://net-internals/#hsts — query and delete the domain pin. Drop server HSTS max-age (e.g. 300s) and reload.
Summary
Nginx HTTP security headers are a server-side control plane. Seven headers (HSTS, X-Frame-Options, X-Content-Type-Options, CSP, Referrer-Policy, Permissions-Policy, X-XSS-Protection) cover transport crypto through content isolation. Ship the conf in CI/CD. Rescan with Mozilla Observatory so the baseline does not regress.