nginx
Nginx SSL/TLS Termination Configuration
Secure Nginx SSL/TLS termination config with modern cipher suites, HSTS, OCSP stapling, and A+ SSL Labs rating.
Overview
SSL/TLS termination handles encryption at the Nginx layer so backend services receive plain HTTP. This configuration uses modern security defaults, achieves an A+ rating on SSL Labs, and includes OCSP stapling for faster certificate verification.
Configuration
# /etc/nginx/conf.d/ssl.conf
# SSL session settings (shared across all server blocks)
ssl_session_cache shared:SSL:10m; # 10MB shared cache (~40,000 sessions)
ssl_session_timeout 1d; # Sessions valid for 1 day
ssl_session_tickets off; # Disable tickets for forward secrecy
# Modern TLS protocols only
ssl_protocols TLSv1.2 TLSv1.3; # Drop TLSv1.0 and TLSv1.1
# Cipher suite configuration
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;
ssl_prefer_server_ciphers off; # Let client choose (TLSv1.3 handles this)
# OCSP stapling — server fetches certificate status instead of client
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s; # DNS resolver for OCSP
resolver_timeout 5s;
# Diffie-Hellman parameter for DHE ciphers
ssl_dhparam /etc/nginx/dhparam.pem; # Generate: openssl dhparam -out /etc/nginx/dhparam.pem 2048
# /etc/nginx/sites-available/secure-app.conf
server {
listen 80;
server_name example.com www.example.com;
# ACME challenge for Let's Encrypt renewal
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect everything else to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# Certificate files
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# HSTS — tell browsers to always use HTTPS (2 years)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Additional security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;
# Proxy to backend
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Key Options Explained
ssl_session_cache shared:SSL:10m— Shares TLS session data across worker processes, allowing session resumption without a full handshake.ssl_session_tickets off— Disabling session tickets ensures forward secrecy is maintained. Session cache still provides resumption.ssl_stapling on— Nginx fetches the OCSP response from the CA and sends it during the TLS handshake, saving the client a round trip.ssl_dhparam— A custom Diffie-Hellman group prevents common DH attacks. Generate withopenssl dhparam -out dhparam.pem 2048.- HSTS
preload— Once submitted to the HSTS preload list, browsers will never connect over HTTP. Only enable after confirming HTTPS works.
Common Modifications
- Wildcard certificates: Use
*.example.comcerts withserver_name *.example.comfor subdomains. - Client certificate auth: Add
ssl_client_certificate /path/to/ca.pem;andssl_verify_client on;for mutual TLS. - TLSv1.3 only: Set
ssl_protocols TLSv1.3;if you only need to support modern browsers. - Certificate auto-renewal: Use certbot with
--webroot -w /var/www/certbotand a cron job or systemd timer.