Migration & Upgrades

Migrating from TLS 1.2 to TLS 1.3: Configuration and Compatibility

How to upgrade your TLS configuration from 1.2 to 1.3 — server configuration, cipher suite updates, middlebox compatibility, and performance gains.

Why TLS 1.3 Is a Security and Performance Upgrade

TLS 1.3 (RFC 8446, published 2018) is not a minor revision — it is a fundamental redesign that eliminates the legacy complexity accumulated over TLS 1.0/1.1/1.2's 25-year history.

Performance: Faster Handshake

TLS 1.2 handshake (2-RTT):
  Client → Server: ClientHello
  Server → Client: ServerHello, Certificate, ServerHelloDone
  Client → Server: ClientKeyExchange, ChangeCipherSpec, Finished  [RTT 1]
  Server → Client: ChangeCipherSpec, Finished
  Client → Server: HTTP Request                                    [RTT 2]

TLS 1.3 handshake (1-RTT):
  Client → Server: ClientHello + key_share
  Server → Client: ServerHello + key_share, Certificate, Finished [RTT 1]
  Client → Server: Finished + HTTP Request                         [RTT 1]

TLS 1.3 eliminates one full round-trip from the handshake. On a 100ms latency connection, that is 100ms off every new TLS connection — significant for mobile users.

Security: Removed Insecure Algorithms

TLS 1.3 removed everything that security researchers had shown to be vulnerable:

Removed from TLS 1.3Why
RSA key exchangeNo forward secrecy — private key theft decrypts all past traffic
CBC mode ciphersBEAST, LUCKY13, POODLE attacks
RC4Statistically broken, NOMORE attack
SHA-1 in signaturesCollision attacks practical since 2017
Export-grade ciphersFREAK, Logjam attacks
CompressionCRIME attack (leaks cookie values via compression side-channel)

Forward Secrecy: Always On

TLS 1.3 mandates ephemeral Diffie-Hellman key exchange (ECDHE) for all connections. This means even if an attacker records encrypted traffic today and steals the server's private key in the future, they cannot decrypt the previously recorded sessions. TLS 1.2 with RSA key exchange had no such protection.

Step 1: Server Configuration

Nginx

server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate     /etc/ssl/certs/your-domain.crt;
    ssl_certificate_key /etc/ssl/private/your-domain.key;

    # Support both TLS 1.2 and 1.3 during transition period:
    ssl_protocols TLSv1.2 TLSv1.3;

    # Once compatibility is confirmed, restrict to TLS 1.3 only:
    # ssl_protocols TLSv1.3;

    # TLS 1.2 cipher suites (keep strong ones for backward compatibility):
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:'
                'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers on;

    # TLS 1.3 cipher suites are configured separately (limited set, all secure):
    # TLS_AES_128_GCM_SHA256
    # TLS_AES_256_GCM_SHA384
    # TLS_CHACHA20_POLY1305_SHA256
    # (These are the only options in TLS 1.3 — no configuration needed)

    # OCSP Stapling (reduces handshake latency by ~100ms):
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

    # Session resumption (critical for TLS 1.3 0-RTT):
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
}

Check which OpenSSL version supports TLS 1.3:

openssl version  # TLS 1.3 requires OpenSSL 1.1.1+
nginx -V 2>&1 | grep -o 'OpenSSL [^ ]*'  # Nginx's OpenSSL version

Apache httpd

# /etc/apache2/sites-available/your-site.conf
<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/your-domain.crt
    SSLCertificateKeyFile /etc/ssl/private/your-domain.key

    # Enable TLS 1.2 and 1.3:
    SSLProtocol -all +TLSv1.2 +TLSv1.3

    # Strong TLS 1.2 cipher suites for backward compatibility:
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder on

    # TLS 1.3 specific cipher suites:
    SSLCipherSuite TLSv1.3 TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
</VirtualHost>

Step 2: Cipher Suite Selection

TLS 1.3 Cipher Suites

TLS 1.3 restricts the cipher suite list to only AEAD (Authenticated Encryption with Associated Data) ciphers. There are exactly three options — all are excellent:

Cipher SuiteAlgorithmKey SizeNotes
`TLS_AES_128_GCM_SHA256`AES-128-GCM128-bitFastest on hardware-accelerated systems
`TLS_AES_256_GCM_SHA384`AES-256-GCM256-bitLarger key, minimal overhead
`TLS_CHACHA20_POLY1305_SHA256`ChaCha20-Poly1305256-bitFaster on mobile/embedded without AES-NI

You do not need to configure TLS 1.3 cipher suites — all three are enabled by default in OpenSSL 1.1.1+. Servers negotiating TLS 1.3 automatically pick the strongest mutually supported option.

TLS 1.2 Backward-Compatible Configuration

For TLS 1.2 clients (enterprise systems, older mobile apps), maintain a strong cipher suite list that excludes the broken ciphers:

# Mozilla SSL Configuration Generator recommended 'Intermediate' profile for TLS 1.2:
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

# Verify your cipher configuration:
openssl ciphers -v 'ECDHE-ECDSA-AES128-GCM-SHA256:...' | awk '{print $1, $2}'

Exclude cipher strings to avoid:

NULL          # No encryption
!aNULL        # No authentication
!EXPORT       # Export-grade (40-bit) ciphers
!RC4          # RC4 stream cipher (broken)
!3DES         # Triple-DES (SWEET32 attack)
!MD5          # MD5 MACs (broken)
!CBC          # CBC mode suites (timing attacks in TLS 1.2)

Step 3: Compatibility Testing

SSL Labs Scan

Qualys SSL Labs provides the industry standard TLS configuration assessment:

# Command-line SSL Labs scan using their API:
curl -s 'https://api.ssllabs.com/api/v3/analyze?host=your-domain.com&all=done' | \
  python3 -c "import sys,json; d=json.load(sys.stdin); \
  print('Grade:', d['endpoints'][0]['grade'])"

# Or use the web interface:
# https://www.ssllabs.com/ssltest/analyze.html?d=your-domain.com

Target: A+ rating. Requirements:

  • TLS 1.2 minimum (disable TLS 1.0/1.1)
  • TLS 1.3 supported
  • HSTS header with includeSubDomains and preload
  • No weak cipher suites
  • Valid, not-expired certificate chain

Client Support Matrix

ClientTLS 1.3 SupportTLS 1.2 Minimum
Chrome 70+YesYes
Firefox 63+YesYes
Safari 12.1+YesYes
Android 10+YesYes
Android 4.xNoTLS 1.0 only — will break if you require TLS 1.2
Java 8u261+YesYes
Java 8 < u261NoTLS 1.2 needs manual enable
.NET 4.7+YesYes
.NET 4.5NoTLS 1.2 (manual config)
OpenSSL 1.1.1+YesYes
curl 7.52+YesYes

Enterprise Middlebox Issues

Corporate security appliances (DPI firewalls, SSL inspection proxies) that were not updated for TLS 1.3 may drop connections. This manifests as connections that work from the office Wi-Fi but not from home, or vice versa.

# Test TLS 1.3 connectivity from different network locations:
curl -v --tlsv1.3 https://your-domain.com 2>&1 | grep -E 'TLS|SSL|error'

# Check which TLS version was negotiated:
openssl s_client -connect your-domain.com:443 -tls1_3 < /dev/null 2>&1 | \
  grep 'Protocol\|Cipher'

# Test TLS 1.2 fallback:
openssl s_client -connect your-domain.com:443 -tls1_2 < /dev/null 2>&1 | \
  grep 'Protocol\|Cipher'

Step 4: Monitoring, Metrics, and Rollback

TLS Version Distribution Metrics

Add TLS version to your access logs to track adoption:

log_format tls_log '$remote_addr - [$time_local] "$request" '
                   '$status $body_bytes_sent '
                   'tls=$ssl_protocol cipher=$ssl_cipher';

access_log /var/log/nginx/access.log tls_log;
# Analyze TLS version distribution:
awk '{for(i=1;i<=NF;i++) if($i~/^tls=/) print $i}' \
  /var/log/nginx/access.log | sort | uniq -c | sort -rn

# Expected output after migration:
# 95234 tls=TLSv1.3
#  4123 tls=TLSv1.2
#    43 tls=TLSv1.1   <- legacy, monitor for zero before disabling

HSTS Preloading

Once you are confident in your TLS 1.2+ setup, add HSTS to prevent protocol downgrade attacks and ensure all connections use TLS:

add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';

Submit to the HSTS preload list at https://hstspreload.org — browsers hard-code your domain as HTTPS-only, eliminating the first insecure HTTP request on new browsers.

Rollback Plan

Rollback is simple: re-enable TLS 1.0/1.1 in your ssl_protocols line. Unlike most migrations, TLS configuration changes take effect immediately on Nginx reload (no deploy needed):

# Emergency rollback — add TLS 1.0 and 1.1 back:
sudo sed -i 's/ssl_protocols TLSv1.2 TLSv1.3/ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3/' \
  /etc/nginx/sites-available/your-site
sudo nginx -t && sudo systemctl reload nginx
# Takes effect within seconds — no gunicorn restart needed

A+ Rating Checklist

[ ] TLS 1.2 minimum (1.0/1.1 disabled)
[ ] TLS 1.3 enabled
[ ] No RC4, DES, 3DES, EXPORT ciphers
[ ] ECDHE key exchange (forward secrecy) for TLS 1.2 ciphers
[ ] OCSP stapling enabled
[ ] Certificate chain complete (no intermediate missing)
[ ] HSTS header with max-age >= 180 days
[ ] No mixed content (all page resources over HTTPS)
[ ] SSL Labs grade: A+

Related Protocols

Related Glossary Terms

More in Migration & Upgrades