Nginx - Security Headers and SSL Hardening
Adding HTTP security headers protects visitors from XSS, clickjacking, MIME sniffing and other vectors. Achievable A+ rating on SSL Labs.
Base Security Headers
Add these headers in the http {} block of /etc/nginx/nginx.conf or in each server {}:
# In /etc/nginx/conf.d/security-headers.conf
# Prevents clickjacking (embedding in iframe)
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevents MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Enables browser's XSS filter (legacy, but useful)
add_header X-XSS-Protection "1; mode=block" always;
# Referrer Policy: limits info sent to external sites
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Browser API Permissions (limit camera, microphone, etc.)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# HSTS: force HTTPS for 1 year (only after verifying HTTPS works!)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Content Security Policy (CSP)
CSP is the most powerful header against XSS. It defines where scripts, styles, images, etc. can load from.
Base CSP (Restrictive)
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';
" always;
CSP for WordPress / Sites with CDN
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://www.google-analytics.com;
frame-src 'self' https://www.youtube.com;
" always;
Test CSP in report-only mode before applying it: Content-Security-Policy-Report-Only. This way you won't break your site.
SSL/TLS Hardening
sudo nano /etc/nginx/conf.d/ssl-hardening.conf
# Protocols: only TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# Secure cipher suite (Mozilla Modern)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Session cache (improves SSL performance)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling (faster certificate revocation check)
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# DH params (additional security)
# Generate with: sudo openssl dhparam -out /etc/ssl/dhparam.pem 2048
ssl_dhparam /etc/ssl/dhparam.pem;
Complete Secure Virtual Host Configuration
server {
listen 80;
server_name mysite.com www.mysite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name mysite.com www.mysite.com;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Hide Nginx version
server_tokens off;
# Limit HTTP methods
if ($request_method !~ ^(GET|POST|HEAD|PUT|DELETE|PATCH)$) {
return 405;
}
root /var/www/mysite.com;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
}
Hide Nginx and PHP Version
# nginx.conf → http {}
server_tokens off;
# /etc/php/8.x/fpm/php.ini
expose_php = Off
Rate Limiting
# nginx.conf → http {}
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# In server {} or location {}
location /wp-login.php {
limit_req zone=login burst=3 nodelay;
try_files $uri $uri/ =404;
}
location /api/login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://localhost:3000;
}
Verify SSL Rating
After configuration, test on:
- SSL Labs: detailed SSL/TLS rating
- Security Headers: HTTP header analysis
- Mozilla Observatory: comprehensive web security analysis
Gerelateerde artikelen
Base Server Hardening
Checklist of fundamental security operations to secure a new VPS before putting it into production
Fail2ban: Brute Force Protection
How to install and configure Fail2ban to protect your server from SSH and web brute force attacks
Change SSH Port
How to change SSH port to reduce automatic brute force attempts from bots and scanners on the internet
