TLS 1.3 vs TLS 1.2: What Actually Changed and Why It Matters

I remember the first time I configured TLS 1.3 support — I added TLSv1.3 to my Nginx config, reloaded, and thought “that’s it?”. On the surface it’s almost invisible. But what changed under the hood is significant, and understanding the differences has shaped how I think about TLS configuration.

Some Context First

TLS 1.2 came out in 2008. For over a decade it was the standard, and it’s still widely supported. TLS 1.3 was finalized in 2018 (RFC 8446), and most modern systems support it. As of right now, the majority of traffic on the internet uses TLS 1.2 or 1.3, with 1.3 adoption growing steadily.

TLS 1.0 and 1.1 are effectively deprecated — they have known weaknesses and browser vendors stopped supporting them. If your server still offers these, you should disable them.

The Handshake: Where TLS 1.3 Wins Most

In TLS 1.2, the handshake takes two round trips before the client can start sending application data. Client says hello, server responds with its certificate and cipher choices, client sends key exchange material, they agree on keys, and only then does the actual HTTP request happen.

TLS 1.3 reduced this to one round trip. The client includes key exchange material in its first message (it predicts what the server will accept). The server responds with its certificate and the session keys are derived. Then application data starts flowing.

This sounds like an implementation detail but it has real-world impact. The first time a user connects to your server, TLS 1.3 is about 100ms faster than TLS 1.2 on a typical internet connection. For users with high latency — mobile on a marginal connection, users far from your server — this matters.

0-RTT Resumption

TLS 1.3 also supports 0-RTT (zero round-trip time) resumption for returning connections. If a client connected recently, they can start sending data immediately in the first message, before waiting for server confirmation.

The catch: 0-RTT data is vulnerable to replay attacks. An attacker who captures a 0-RTT request can replay it. This is fine for idempotent requests (GET, HEAD), problematic for non-idempotent ones (POST, payment processing, etc.).

I typically enable 0-RTT in Nginx only for read-heavy applications and make sure my application logic is aware of the Early-Data header that Nginx sets when 0-RTT is in use:

ssl_early_data on;
proxy_set_header Early-Data $ssl_early_data;

What Happened to All Those Cipher Suites

TLS 1.2 has a huge list of cipher suites — RSA key exchange, DHE, ECDHE, dozens of symmetric ciphers, various MAC algorithms. A big part of TLS 1.2 configuration is picking which cipher suites to allow. Getting it wrong (allowing weak ciphers like RC4, 3DES, or cipher suites with no forward secrecy) creates vulnerabilities.

TLS 1.3 simplified this dramatically. It supports exactly five cipher suites, all of which are strong:

  • TLS_AES_128_GCM_SHA256
  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_CCM_SHA256
  • TLS_AES_128_CCM_8_SHA256

You don’t need to configure cipher suites for TLS 1.3 — they’re all good choices. This eliminates an entire class of misconfiguration. All TLS 1.3 cipher suites use AEAD (Authenticated Encryption with Associated Data), which is stronger than the MAC-then-encrypt pattern used by weaker TLS 1.2 cipher suites.

Forward Secrecy Is Now Mandatory

In TLS 1.2, you could configure cipher suites without forward secrecy — RSA key exchange, for example. If an attacker recorded encrypted traffic and later obtained the server’s private key, they could decrypt all past sessions.

TLS 1.3 removed RSA key exchange entirely. Every connection uses an ephemeral key exchange (ECDHE or DHE), which means session keys are unique and not derivable from the server’s long-term private key. Forward secrecy is baked in.

This is a huge deal for privacy. Even if your server’s private key is compromised years from now, past TLS 1.3 connections can’t be decrypted by anyone who recorded the traffic.

Configuring TLS 1.3 in Nginx

This snippet fits into a broader Nginx SSL configuration — add it to your server block alongside your certificate paths:

ssl_protocols TLSv1.2 TLSv1.3;

# Cipher suites for TLS 1.2 (1.3 handles its own)
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;

# TLS session cache and tickets
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

ssl_prefer_server_ciphers off for TLS 1.3 lets the client indicate its preferred cipher, which is usually fine — all TLS 1.3 ciphers are good. For TLS 1.2, I still provide an ordered list of strong cipher suites.

ssl_session_tickets off is a privacy and forward secrecy consideration. Session tickets are encrypted with a server-side key that doesn’t get rotated as often as you’d like. Disabling them forces full resumption via session IDs instead, which has better forward secrecy properties. The performance tradeoff is minor.

Checking Which Version a Client Uses

When debugging, I sometimes want to see which TLS version clients are actually using. You can log it with Nginx:

log_format tls_info '$remote_addr - $ssl_protocol $ssl_cipher';
access_log /var/log/nginx/tls_info.log tls_info;

A few days of logs from a production server gives a good picture of your client distribution. In my experience, it’s usually something like 70-80% TLS 1.3, the rest TLS 1.2, and a tiny tail of older clients.

Can I Drop TLS 1.2 Yet?

I get asked this occasionally. The short answer is: probably not if you want to serve everyone.

TLS 1.3 support is excellent in modern browsers and operating systems. But Android 4 and older don’t support it. Windows 7 with IE11 doesn’t support it. Some enterprise clients with outdated configurations don’t support it. Old IoT devices, network appliances, and corporate proxies sometimes struggle with TLS 1.3.

My current recommendation: support both TLS 1.2 and TLS 1.3. Disable TLS 1.0 and 1.1 unconditionally. For the subset of infrastructure that you fully control on both ends (internal services, known client environments), you can go TLS 1.3 only with confidence.

Apache Configuration

For completeness, the equivalent in Apache (for a full Apache SSL walkthrough, see Installing and Configuring SSL in Apache):

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
SSLCipherSuite TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384
SSLHonorCipherOrder off

Apache lets you specify cipher suites per TLS version separately, which is cleaner than Nginx’s approach.

Testing Your TLS Configuration

After making changes, verify:

openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_1  # should fail
openssl s_client -connect example.com:443 -tls1    # should fail

Online tools like SSL Labs give a detailed report with a grade and specific issues flagged — worth running periodically even if you didn’t change anything, since their recommendations evolve.


The main takeaway from TLS 1.3 is that it’s strictly better than 1.2 in every meaningful way: faster, simpler cipher negotiation, mandatory forward secrecy, and a cleaner protocol design. Enable it, keep 1.2 for compatibility, and drop everything older. That’s the practical default that gets you most of the benefit with minimal disruption.

Scroll to Top