How to Force TLS 1.2 Only in Nginx: Disabling TLS 1.0/1.1 Properly


6 views

Many administrators encounter a puzzling situation where Nginx continues to accept TLS 1.0/1.1 connections despite explicit configuration to allow only TLS 1.2. This behavior typically stems from either misconfiguration or version-specific quirks in the OpenSSL/Nginx interaction.

Your current configuration looks technically correct at first glance:

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM256:DH+AESGCM256:ECDH+AES256:!aNULL:!MD5:!kEDH;

Several factors could be causing this behavior:

  • Version mismatch: Older Nginx versions (especially pre-1.9.1) may require different syntax
  • Configuration inheritance: Check for multiple ssl_protocols directives in included files
  • OpenSSL limitations: Some OpenSSL versions don't properly honor protocol restrictions

For Nginx 1.7.10 with OpenSSL 1.0.1f, use this definitive configuration:

ssl_protocols TLSv1.2;
ssl_ciphers '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_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

After applying changes, verify with:

openssl s_client -connect yourdomain.com:443 -tls1
openssl s_client -connect yourdomain.com:443 -tls1_1
openssl s_client -connect yourdomain.com:443 -tls1_2

Only the TLS 1.2 test should succeed with a valid certificate chain.

For current Nginx versions (1.19.4+), consider:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
ssl_ecdh_curve X25519:secp521r1:secp384r1;
ssl_prefer_server_ciphers on;

When configuring Nginx to use only TLS 1.2, many administrators encounter a frustrating situation where older protocols remain available despite explicit configuration. Here's what's happening under the hood:

# Common but problematic configuration
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

The issue typically stems from three potential sources:

  • OpenSSL version dependencies (1.0.1 series has known limitations)
  • Configuration inheritance in Nginx server blocks
  • Missing server restarts or configuration reloads

Here's a verified configuration that properly enforces TLS 1.2:

server {
    listen 443 ssl;
    server_name example.com;
    
    # SSL Configuration
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    
    # Certificate paths
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
}

After implementing the configuration, verify using these tools:

# Using OpenSSL client
openssl s_client -connect example.com:443 -tls1
openssl s_client -connect example.com:443 -tls1_1
openssl s_client -connect example.com:443 -tls1_2

# Using online tools
curl -v https://example.com --tlsv1.2
curl -v https://example.com --tlsv1.1

Problem: Configuration appears correct but old protocols still work
Solution: Check for multiple ssl_protocols directives in included files

Problem: OpenSSL version limitations
Solution: Upgrade to OpenSSL 1.1.0+ where possible

# Check OpenSSL version
openssl version

While enforcing TLS 1.2 improves security, consider these optimizations:

  • Enable OCSP stapling to reduce handshake time
  • Implement session tickets for better performance
  • Consider TLS 1.3 for modern clients
# OCSP Stapling configuration
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;