SSL/TLS: Configuration & Hardening
Nginx TLS Configuration
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on; # enable HTTP/2
server_name example.com www.example.com;
# Certificate chain (leaf + intermediates)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS configuration (Mozilla Intermediate)
ssl_protocols TLSv1.2 TLSv1.3;
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; # TLS 1.3 ignores this; off = client chooses
# Session resumption
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # disable for forward secrecy
# OCSP Stapling
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;
# DH params (for DHE cipher suites)
ssl_dhparam /etc/ssl/dhparam.pem; # openssl dhparam -out dhparam.pem 2048
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
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 Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
# HTTP → HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}HSTS & Preloading
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age=63072000 — browser remembers HTTPS-only for 2 years
includeSubDomains — applies to all subdomains
preload — eligible for browser preload list (hardcoded HTTPS, no HTTP ever)
Preload list:
- Submit at https://hstspreload.org
- Removes even the first-visit HTTP downgrade risk
- Irreversible in the short term — ensure ALL subdomains support HTTPS first
- Currently in Chrome, Firefox, Safari, Edge preload lists
Rollout strategy:
1. Start: max-age=300 (5 min) — test, ensure no HTTP subdomains
2. Increase: max-age=86400 (1 day)
3. Add includeSubDomains: verify all subs have valid certs
4. Production: max-age=63072000; includeSubDomains
5. Submit for preload: add preload directiveMutual TLS (mTLS)
mTLS requires both server and client to present certificates — not just the server. Used for service-to-service authentication in microservices (zero-trust networks), API authentication for partners.
# Server requires client certificate
server {
ssl_client_certificate /etc/ssl/ca-chain.pem; # CA that signed client certs
ssl_verify_client on;
ssl_verify_depth 2;
location /api/ {
# Client cert details in headers for upstream
proxy_set_header X-Client-CN $ssl_client_s_dn_cn;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_pass http://backend;
}
}# Generate client certificate (dev/testing)
# 1. Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=My CA"
# 2. Create client key + CSR
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=service-a"
# 3. Sign with CA
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
# 4. Test
curl --cert client.crt --key client.key https://api.example.com/Testing & Debugging
# Check certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -text
# Check expiry
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -dates
# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Online tools
# https://www.ssllabs.com/ssltest/ — full TLS config audit (A+ rating guide)
# https://securityheaders.com — security headers check
# https://whatsmychaincert.com — certificate chain validator
# Decode a certificate
openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -subject -issuer -dates -fingerprintKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free