Many sysadmins face this dilemma: their nginx configuration achieves a perfect 100% score on SSL Labs with TLS 1.2 alone, but adding TLS 1.3 suddenly introduces scoring penalties. The root cause lies in how SSL Labs evaluates cipher suites differently when both protocols are enabled.
SSL Labs applies stricter criteria when TLS 1.3 is present. While CBC cipher suites might be acceptable for backward compatibility in TLS 1.2-only configurations, they trigger warnings when TLS 1.3 is enabled because:
- TLS 1.3 completely removed CBC mode ciphers
- The presence of older cipher suites suggests potential downgrade attacks
After extensive testing, here's the configuration that maintains compatibility while maximizing security:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_ecdh_curve X25519:secp521r1:secp384r1:secp256k1;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
This configuration achieves several security goals:
- Prioritizes modern AEAD ciphers (GCM, ChaCha20)
- Includes strong ECDHE key exchange with X25519
- Disables problematic CBC-mode ciphers while maintaining compatibility
- Enables OCSP stapling for better performance
When implementing this configuration, verify with:
nginx -t && systemctl reload nginx
openssl s_client -connect yourdomain.com:443 -tls1_3
ssllabs-scan yourdomain.com
The expected results should show:
- All TLS 1.3 cipher suites preferred
- Fallback to strong TLS 1.2 ciphers when needed
- No CBC-mode ciphers in the negotiation chain
For environments requiring support for very old clients, consider these alternatives:
- Implement separate server blocks for legacy clients
- Use Cloudflare or similar CDN to handle legacy TLS
- Maintain two separate configurations with different hostnames
Many sysadmins face a peculiar challenge when trying to maintain a perfect 100% SSL Labs score while enabling both TLS 1.2 and 1.3 in Nginx. The issue stems from how Qualys SSL Labs evaluates cipher suites when both protocols are active.
SSL Labs assigns different weights to cipher suites based on:
- Encryption strength (AES-256 vs AES-128)
- Key exchange mechanisms (ECDHE vs DHE)
- MAC algorithms (SHA-384 vs SHA-1)
- Cipher modes (GCM vs CBC)
When you enable TLS 1.3, the scanner becomes more stringent about CBC-mode ciphers in TLS 1.2, even though these might be necessary for backward compatibility.
Here's a configuration that achieves the best possible balance between security and compatibility:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
The key elements that make this configuration effective:
- TLS 1.3 Ciphersuites First: We list the TLS 1.3 ciphers first as they're the most secure
- Explicit GCM-mode Ciphers: We only include AEAD ciphers for TLS 1.2
- Strong Elliptic Curves: Using secp384r1 provides sufficient security margin
- No CBC-mode Ciphers: We completely avoid CBC-mode ciphers which trigger warnings
Some older clients might fail to connect with this strict configuration. If you must support legacy systems, consider implementing a fallback virtual host with more permissive settings, or use a reverse proxy that can handle legacy clients separately.
After implementing these changes:
- Test your configuration with:
nginx -t
- Reload Nginx:
systemctl reload nginx
- Verify with SSL Labs:
https://www.ssllabs.com/ssltest/
- Check real-world compatibility with:
https://clienttest.ssllabs.com:8443/ssltest/viewMyClient.html
If you're still seeing issues, consider these additional optimizations:
# For even stricter security (might break more clients)
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# If you need to support very old clients (not recommended)
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
Remember that security is always a balance between theoretical perfection and practical compatibility. The configuration presented here represents what we've found to be the best compromise in most production environments.