Nginx SSL/TLS Configuration
SSL configuration in Nginx combines multiple directives: specifying the certificate, selecting TLS protocol versions, choosing cipher suites, and adding the HSTS header. Supporting both TLS 1.2 and TLS 1.3 while disabling the older TLS 1.0 and 1.1 is the current security best practice. To achieve an A+ rating on SSL Labs (ssllabs.com/ssltest/), you need to configure HSTS, OCSP stapling, and cipher suites that support Forward Secrecy.
Syntax
# -----------------------------------------------
# Certificate and private key
# -----------------------------------------------
# ssl_certificate {path to certificate file}
# → Specifies the path to the server certificate (PEM format)
# → Use a full-chain certificate that includes intermediate certificates
# Example: ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key {path to private key file}
# → Specifies the path to the private key (PEM format) corresponding to the certificate
# Example: ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# -----------------------------------------------
# TLS protocol versions
# -----------------------------------------------
# ssl_protocols {protocol name ...}
# → Specifies the TLS protocol versions to allow
# → Disable TLSv1.0 and TLSv1.1 due to known vulnerabilities
# → Supporting both TLSv1.2 and TLSv1.3 is the current best practice
# Example: ssl_protocols TLSv1.2 TLSv1.3;
# -----------------------------------------------
# Cipher suites
# -----------------------------------------------
# ssl_ciphers {colon-separated list of cipher suites}
# → Specifies the cipher suites to allow, separated by colons
# → Prioritize ECDHE / DHE cipher suites that support Forward Secrecy
# → Exclude weak ciphers such as RC4, 3DES, NULL, and EXPORT
# Example: ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# ssl_prefer_server_ciphers {on|off}
# → When set to on, the server's cipher preference takes priority over the client's
# → This setting is ignored for TLSv1.3
# Example: ssl_prefer_server_ciphers on;
# -----------------------------------------------
# HSTS header
# -----------------------------------------------
# add_header Strict-Transport-Security {value} always;
# → Adds a response header that enforces HTTPS connections
# → max-age: number of seconds the browser remembers to use HTTPS (31536000 = 1 year)
# → includeSubDomains: also applies to subdomains
# → preload: required for registration in the HSTS preload list (Chrome, etc.)
# Example: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# -----------------------------------------------
# SSL session cache and timeout
# -----------------------------------------------
# ssl_session_cache {type:size}
# → Specifies the SSL session cache type and size
# → shared:SSL:10m creates a 10 MB cache shared across all worker processes
# → Stores approximately 4,000 sessions per MB
# Example: ssl_session_cache shared:SSL:10m;
# ssl_session_timeout {duration}
# → Specifies the expiration time for cached SSL sessions
# → Default is 5 minutes. A value of 10m (10 minutes) is common
# Example: ssl_session_timeout 10m;
# -----------------------------------------------
# OCSP stapling
# -----------------------------------------------
# ssl_stapling {on|off}
# → Enables OCSP stapling
# → The server pre-fetches certificate revocation information (OCSP response)
# and delivers it to the client during the TLS handshake
# → Eliminates the need for the client to query the CA directly, speeding up the connection
# Example: ssl_stapling on;
# ssl_stapling_verify {on|off}
# → Enables signature verification of the OCSP response fetched by the server
# → Use together with ssl_stapling on
# Example: ssl_stapling_verify on;
# resolver {DNS server IP} valid={cache seconds}
# → Specifies the DNS server used to resolve the CA's OCSP server for OCSP stapling
# Example: resolver 8.8.8.8 8.8.4.4 valid=300s;
# -----------------------------------------------
# DH parameter file (for TLSv1.2)
# -----------------------------------------------
# ssl_dhparam {path to DH parameter file}
# → Specifies the Diffie-Hellman parameter file used by DHE cipher suites
# → 2048 bits or more is recommended
# → Generate: sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# Example: ssl_dhparam /etc/nginx/dhparam.pem;
Directive reference
| Directive | Description |
|---|---|
ssl_certificate | Specifies the path to the server certificate (PEM format). Use a full-chain certificate that includes intermediate certificates. |
ssl_certificate_key | Specifies the path to the private key (PEM format) corresponding to the certificate. Set file permissions to 600. |
ssl_protocols | Specifies the TLS protocol versions to allow. Using TLSv1.2 TLSv1.3 is the current best practice. |
ssl_ciphers | Specifies the cipher suites to allow, separated by colons. Prioritize ECDHE / DHE cipher suites that support Forward Secrecy. |
ssl_prefer_server_ciphers | When set to on, the server's cipher preference takes priority over the client's. Ignored for TLSv1.3. |
add_header Strict-Transport-Security | Adds the HSTS header to enforce HTTPS connections. Configure using a combination of max-age, includeSubDomains, and preload. |
ssl_session_cache | Specifies the SSL session cache type and size. shared:SSL:10m creates a 10 MB cache shared across all worker processes. |
ssl_session_timeout | Specifies the expiration time for cached SSL sessions. A value of 10m (10 minutes) is common. |
ssl_stapling | Enables OCSP stapling. The server pre-fetches certificate revocation information and delivers it to the client, speeding up the TLS handshake. |
ssl_stapling_verify | Enables signature verification of the OCSP response fetched by the server. Use together with ssl_stapling on. |
resolver | Specifies the IP address of the DNS server used to resolve the CA's OCSP server for OCSP stapling. |
ssl_dhparam | Specifies the path to the Diffie-Hellman parameter file used by DHE cipher suites. 2048 bits or more is recommended. |
Example
/etc/nginx/sites-available/akane-ssl.conf
# -----------------------------------------------
# HTTP → HTTPS redirect
# -----------------------------------------------
# Redirects all HTTP requests to akane.example.com
# to HTTPS with a 301 permanent redirect
# -----------------------------------------------
server {
listen 80;
listen [::]:80;
server_name akane.example.com;
# Redirect all HTTP requests to HTTPS permanently
return 301 https://$host$request_uri;
}
# -----------------------------------------------
# HTTPS server block (TLS 1.2 / 1.3 support)
# -----------------------------------------------
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name akane.example.com;
# -----------------------------------------------
# Document root and index files
# -----------------------------------------------
root /var/www/akane.example.com/html;
index index.php index.html;
# -----------------------------------------------
# Certificate and private key
# Uses a full-chain certificate obtained with Let's Encrypt
# -----------------------------------------------
ssl_certificate /etc/letsencrypt/live/akane.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/akane.example.com/privkey.pem;
# -----------------------------------------------
# TLS protocol versions
# TLSv1.0 and TLSv1.1 are disabled due to known vulnerabilities
# -----------------------------------------------
ssl_protocols TLSv1.2 TLSv1.3;
# -----------------------------------------------
# Cipher suites
# Prioritizes ECDHE / DHE cipher suites for Forward Secrecy
# Weak ciphers such as RC4, 3DES, and EXPORT are excluded
# -----------------------------------------------
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# Server cipher preference takes priority over the client's (ignored for TLSv1.3)
ssl_prefer_server_ciphers on;
# -----------------------------------------------
# DH parameter file (for DHE cipher suites in TLSv1.2)
# Generate: sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# -----------------------------------------------
ssl_dhparam /etc/nginx/dhparam.pem;
# -----------------------------------------------
# SSL session cache
# Allocates a 10 MB session cache shared across all worker processes
# Stores approximately 40,000 sessions
# -----------------------------------------------
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# -----------------------------------------------
# OCSP stapling
# The server pre-fetches certificate revocation information
# and delivers it to the client during the TLS handshake
# -----------------------------------------------
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# -----------------------------------------------
# Security headers
# -----------------------------------------------
# HSTS: enforces HTTPS for 1 year (31,536,000 seconds)
# includeSubDomains applies this to subdomains as well
# preload is required for HSTS preload list registration
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Clickjacking protection
add_header X-Frame-Options "SAMEORIGIN" always;
# MIME type sniffing protection
add_header X-Content-Type-Options "nosniff" always;
# XSS protection (for modern browsers)
add_header X-XSS-Protection "1; mode=block" always;
# -----------------------------------------------
# PHP-FPM integration
# -----------------------------------------------
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param HTTPS on;
}
# -----------------------------------------------
# Let's Encrypt certificate renewal directory
# -----------------------------------------------
location ~ /.well-known/acme-challenge/ {
allow all;
root /var/www/html;
}
}
Run the following command:
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful $ sudo systemctl reload nginx $ curl -I https://akane.example.com HTTP/2 200 strict-transport-security: max-age=31536000; includeSubDomains; preload x-frame-options: SAMEORIGIN x-content-type-options: nosniff x-xss-protection: 1; mode=block
Generating the DH parameter file and checking the SSL Labs rating
# ----------------------------------------------- # Generate the DH parameter file (first time only) # Generating 2048 bits may take a few minutes # ----------------------------------------------- # Generate the parameter file for DHE cipher suites sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048 # Verify the file was created ls -lh /etc/nginx/dhparam.pem # ----------------------------------------------- # Syntax check and reload # ----------------------------------------------- # Check the configuration file for syntax errors sudo nginx -t # Reload the configuration after a successful syntax check (without dropping connections) sudo systemctl reload nginx # ----------------------------------------------- # Online test with SSL Labs # https://www.ssllabs.com/ssltest/analyze.html?d=akane.example.com # Visit the URL above to verify an A+ rating # ----------------------------------------------- # Check TLS connection details using the openssl command openssl s_client -connect akane.example.com:443 -tls1_2 < /dev/null 2>&1 | grep "Protocol\|Cipher" openssl s_client -connect akane.example.com:443 -tls1_3 < /dev/null 2>&1 | grep "Protocol\|Cipher"
Run the following command:
$ ls -lh /etc/nginx/dhparam.pem
-rw-r--r-- 1 root root 424 Mar 25 12:00 /etc/nginx/dhparam.pem
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
$ openssl s_client -connect akane.example.com:443 -tls1_3 < /dev/null 2>&1 | grep "Protocol\|Cipher"
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Overview
For Nginx SSL configuration, the foundation is to restrict TLS versions with ssl_protocols and allow only secure cipher suites with ssl_ciphers. TLSv1.0 and TLSv1.1 have been deprecated by major browsers since 2018, and the combination of TLSv1.2 TLSv1.3 is now the standard configuration. Adding the HSTS header (Strict-Transport-Security) tells the browser to remember HTTPS for one year, protecting against protocol downgrade attacks. Enabling OCSP stapling removes the need for the client to query the CA directly, which can speed up the TLS handshake and improve privacy. For obtaining certificates, Let's Encrypt / Certbot provides free certificates with automatic renewal. For general Nginx configuration, see Nginx overview. For performance tuning, see Nginx performance settings.
If you find any errors or copyright issues, please contact us.