Adam Innes · Blog

TLS 1.3 Is Now RFC 8446: What It Cuts, What It Speeds Up, and When Your Server Gets It

· 7 min · security, tls, openssl, nginx, apache

Last Friday, August 10, the IETF published TLS 1.3 as RFC 8446. Mozilla’s post on Monday says Firefox 61 already ships draft 28, which it calls essentially the final version with a different version number, and that the final version is expected in Firefox 63 in October. It also says both Firefox and Chrome have fielded draft versions.

If you run servers, the natural questions are what changed and whether you can flip it on this week. I went through the RFC and the official OpenSSL, nginx, and Apache httpd docs as they stand today. The short version: the protocol is a real cleanup, and on the server side most of us are waiting on one library release.

What the spec throws out

The RFC’s list of major differences from TLS 1.2 reads like a decade of accumulated options being swept out. Symmetric encryption is pruned down to AEAD (authenticated encryption with associated data) algorithms only, which leaves no place for the CBC mode suites with a separate MAC or for RC4. On RC4 the RFC goes further: implementations must not offer or negotiate it in any version of TLS.

Static RSA and static Diffie-Hellman key exchange are gone too, and the RFC says every public key based key exchange now provides forward secrecy. Compression, DSA, and custom ephemeral Diffie-Hellman groups are removed, and renegotiation is forbidden. Session IDs, session tickets, and the old PSK cipher suites have been folded into a single pre-shared key exchange.

Your RSA certificate still works. The RFC’s mandatory list includes RSA signatures, using RSASSA-PSS for the handshake signature, alongside ECDSA with P-256. The difference is that the RSA key signs the handshake instead of encrypting the key exchange.

Cipher suites mean less now

A TLS 1.3 cipher suite only names the AEAD algorithm and the hash used for key derivation, following a TLS_AEAD_HASH pattern. The RFC defines five, including TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, and TLS_CHACHA20_POLY1305_SHA256. Key exchange and certificate type are negotiated separately, so they aren’t in the name anymore. TLS 1.3 suites can’t be used with TLS 1.2, and TLS 1.2 suites can’t be used with TLS 1.3, even though they share the same numbering space.

An implementation must support TLS_AES_128_GCM_SHA256 and should support the AES 256 GCM and ChaCha20 Poly1305 suites. For key exchange it must support P-256 and should support X25519.

Where the speed comes from

In a full TLS 1.3 handshake the client doesn’t wait to learn what the server supports. It sends Diffie-Hellman key shares right in the ClientHello, along with its supported versions and cipher suites. The server answers with a ServerHello containing its own key share, and from that point both sides can derive handshake keys. Everything the server sends after the ServerHello is encrypted: the new EncryptedExtensions message, its certificate, CertificateVerify, and Finished. Mozilla sums it up as a handshake that takes one round trip, down from two in TLS 1.2.

That encryption is a privacy win too. The server’s certificate now travels inside the encrypted part of the handshake instead of in the clear.

You can lose the round trip you saved. If the client guessed a group the server doesn’t support, the server sends a HelloRetryRequest and the client starts over with a different key share. OpenSSL’s TLS 1.3 blog post says most clients will open with X25519 or P-256 and recommends servers support at least those two, which is advice I’d follow.

0-RTT comes with a warning label

When a client shares a pre-shared key with the server, usually from an earlier connection, TLS 1.3 lets it send application data in its very first flight. That’s 0-RTT, or early data, and the RFC is blunt about its costs. Early data is encrypted only under keys derived from the pre-shared key, so it isn’t forward secret. There are also no guarantees against replay between connections, because early data doesn’t depend on anything from the server’s reply.

Section 8 describes what servers can do: allow each ticket to be used only once, or record a value derived from each ClientHello and reject duplicates. Servers should do one of those, but the RFC admits not every deployment will keep that kind of shared state, so clients must only send early data that is safe to replay. Implementations must not enable 0-RTT unless the application asks for it, and application protocols need a profile saying which messages are safe to send that way.

My advice is to leave 0-RTT off until you’ve worked out which of your requests are genuinely safe to repeat. A GET for a static page is very different from a POST that places an order.

Downgrade protection

Version negotiation changed too. The old version field in the ClientHello is frozen at the TLS 1.2 value, and the real list of versions goes in a supported_versions extension, because too many servers rejected hellos with a version number they didn’t recognize.

To keep connections from being quietly pushed to an older version, a TLS 1.3 server that negotiates TLS 1.2 or below writes a fixed marker into the last eight bytes of its ServerHello random value. The bytes spell DOWNGRD in ASCII, followed by 01 for TLS 1.2 or 00 for anything older, and a TLS 1.3 client that sees an older version plus that marker aborts. The RFC calls this limited protection on top of what the Finished messages already provide, and notes that in practice many TLS 1.2 clients and servers won’t behave as specified.

Middleboxes shaped the design

The RFC’s middlebox compatibility mode appendix says field measurements found a significant number of middleboxes misbehave when a client and server negotiate TLS 1.3. The workaround makes the handshake look more like TLS 1.2 session resumption: the client always sends a non-empty session ID, and both sides send a dummy change_cipher_spec record that the peer ignores. The RFC is clear that this doesn’t make those middleboxes compliant; a device that terminates TLS must behave as a proper server to the client and a proper client to the server. OpenSSL’s beta man pages say it sends those dummy messages by default.

If your network has a TLS inspecting proxy or firewall, I’d check with the vendor about TLS 1.3 before assuming everything will just work.

Turning it on: OpenSSL comes first

nginx and Apache’s mod_ssl are both built on OpenSSL in the usual setup, so that’s the gate. OpenSSL’s February blog post says the forthcoming OpenSSL 1.1.1 will include TLS 1.3 and will not be released until TLS 1.3 is finalized. It hasn’t shipped yet. The newest pre-release is 1.1.1 pre8 from June 20, and its announcement calls it a beta for testing only, not for security critical use. Its CHANGES file says pre8 implements a draft that won’t interoperate with the final standard, and its header identifies that draft as 28. Final TLS 1.3 in OpenSSL is announced as coming in 1.1.1, not something you can install from a release today.

One detail from those CHANGES is worth knowing now. TLS 1.3 suites are configured separately from the classic cipher string, so an old hand-tuned cipher list can’t accidentally disable all of them. By default pre8 enables the AES 256 GCM, ChaCha20 Poly1305, and AES 128 GCM suites.

nginx

nginx is ready on its side. The ssl_protocols docs say the TLSv1.3 parameter arrived in 1.13.0 and only works with OpenSSL 1.1.1. The documented default right now is TLSv1, TLSv1.1, and TLSv1.2, so TLS 1.3 has to be listed explicitly. Once you’re built against a 1.1.1 release, it’s one line:

ssl_protocols TLSv1.2 TLSv1.3;

Running nginx -V prints a “built with OpenSSL” line so you can check which library your binary uses. Keep in mind that ssl_ciphers sets the classic cipher string, which in 1.1.1 no longer governs TLS 1.3 suites, and nginx currently has no directive for those suites or for early data. The 1.15.2 release on July 24 fixed session reuse with upstream servers over TLS 1.3, so it’s clearly being exercised.

Apache httpd

Apache’s released code is further behind. In the docs for the current 2.4 release, 2.4.34 from July 16, SSLProtocol lists protocols up to TLSv1.2 and nothing newer. The development trunk docs do describe a TLSv1.3 option for SSLProtocol and a TLSv1.3 specifier for SSLCipherSuite, both requiring OpenSSL 1.1.1, but that’s unreleased code. I’d wait for a 2.4 release that documents it.

What I’d do this week

Production TLS 1.3 mostly means waiting for OpenSSL 1.1.1, then for your distribution or build pipeline to pick it up. Some prep carries over now. The RFC says implementations negotiating older versions should prefer forward secret AEAD suites, so trimming RC4 and static RSA key exchange out of your TLS 1.2 config is worth doing today. Make sure your certificates are RSA or ECDSA, since DSA is out. If you want to experiment, a staging box with the beta and openssl s_client -tls1_3 is fine, as long as you remember it speaks draft 28 rather than the final RFC.

TLS 1.3 is smaller, faster, and more private, and most of what it removes is stuff you shouldn’t have been using anyway. Turn it on when 1.1.1 lands, and save 0-RTT for later.

← all posts