Nginx SSL/TLS Configuration: Complete Guide
Nginx is one of the most widely used HTTPS servers thanks to its high-performance TLS handling. This page covers the key directives — ssl_certificate, ssl_protocols, ssl_ciphers — and walks through a complete HTTPS setup including HTTP→HTTPS redirects.
Basic HTTPS Configuration
# /etc/nginx/conf.d/https.conf
server {
listen 443 ssl;
listen [::]:443 ssl; # IPv6
http2 on; # HTTP/2 (Nginx 1.25.1+)
server_name example.com www.example.com;
# Certificate and private key
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
root /var/www/html;
index index.html;
}
}
# HTTP → HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
TLS Version and Cipher Suite
Recommended (TLS 1.2 + 1.3)
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
# TLS versions: allow 1.2 and 1.3 only
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites (for TLS 1.2; TLS 1.3 is configured automatically)
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:'
'!DSS';
# Server cipher preference (off is recommended for TLS 1.3)
ssl_prefer_server_ciphers off;
# ECDH parameters
ssl_ecdh_curve X25519:prime256v1:secp384r1;
# DH parameters (for TLS 1.2 DHE)
# ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -out dhparam.pem 4096
}
TLS 1.3 Only (highest security, blocks some older clients)
ssl_protocols TLSv1.3;
# TLS 1.3 cipher suites cannot be set directly — OpenSSL selects automatically
SSL Session Cache (Performance)
TLS handshakes are computationally expensive. Session caching skips the handshake on reconnection.
# Set in the http block of nginx.conf or a conf.d/ file
http {
# Shared session cache: ~4000 sessions per MB
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d; # Session lifetime: 1 day
ssl_session_tickets off; # Disable for security (protects Forward Secrecy)
}
OCSP Stapling
Normally the browser checks certificate validity by querying an OCSP server on every connection. OCSP Stapling has the server pre-fetch the OCSP response and deliver it to the client, eliminating the extra round trip.
server {
ssl_stapling on;
ssl_stapling_verify on;
# CA chain used to verify the OCSP response
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# DNS resolver for the OCSP server lookup
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
}
Complete Production Nginx HTTPS Configuration
# /etc/nginx/conf.d/example.com.conf
# HTTP → HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Exclude Let's Encrypt renewal path
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# Certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS settings
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';
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
# Session cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# 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 X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Application proxy
location / {
proxy_pass http://backend;
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;
}
# Logs
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log warn;
}
Validation
# Check config syntax
sudo nginx -t
# Apply changes
sudo systemctl reload nginx
# Verify TLS 1.3 is working
openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep "Protocol"
# Protocol : TLSv1.3
# Verify TLS 1.0/1.1 are blocked
openssl s_client -connect example.com:443 -tls1 2>&1 | grep -E "handshake|alert"
# handshake failure ← correct, blocked
# Verify weak cipher is blocked
openssl s_client -connect example.com:443 -cipher 'RC4' 2>&1 | grep "cipher"
# no peer certificate available ← correct, blocked
# External test
# https://www.ssllabs.com/ssltest/analyze.html?d=example.com
www → non-www (or vice versa) Redirect
# www → example.com (prefer non-www)
server {
listen 443 ssl;
http2 on;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
# Main domain
server {
listen 443 ssl;
http2 on;
server_name example.com;
# ... rest of config
}
The next page covers Apache mod_ssl HTTPS configuration.