TLS
the handshake that encrypts a connection
What it is
Transport Layer Security, TLS for short, is the protocol a client and a server run before they exchange any application data, so that what follows is private, cannot be altered unnoticed, and reaches the party the client meant to reach. Product menus and certificate shops still call it SSL, after the Secure Sockets Layer protocol it replaced. RFC 7568 deprecated the last SSL version in June 2015, so anything labelled SSL today is TLS underneath, or ought to be.
TLS sits between an application protocol and the transport below it. HTTPS is HTTP carried inside a TLS connection, and the same layer protects mail submission, IMAP, database connections and the Gemini protocol. HTTP/3 goes further: it runs over QUIC, which builds the TLS 1.3 handshake into the transport itself instead of layering it on TCP, as RFC 9001 describes.
The current version is TLS 1.3, defined in RFC 8446 from August 2018, which replaced the TLS 1.2 specification. A connection opens with a handshake. The client sends a ClientHello listing the versions, cipher suites and key shares it supports, together with Server Name Indication (SNI), the host name it wants, so that one address can serve many sites. The server chooses, answers with its own key share, and from that moment everything is encrypted, including the certificate it presents. That certificate chain ties the server’s key to a certificate authority the client already trusts. The client checks the chain, the name and the dates, and the server proves it holds the matching private key by signing the handshake.
Three things set 1.3 apart from 1.2. A full handshake takes one round trip instead of two. Every key exchange is ephemeral, so a recording of today’s traffic stays sealed even if the server’s private key leaks next year; that property is called forward secrecy. And a returning client may send “early data” with its very first message, known as 0-RTT, at a price the RFC spells out: early data is neither forward secret nor protected against being replayed on another connection.
Why it matters
Using TLS is settled. The decisions that remain are which versions to accept and where the encrypted connection ends.
Versions first. RFC 8996, published in March 2021, says TLS 1.0 and 1.1 must not be used. RFC 9325, the current best practice (BCP 195), requires support for 1.2, recommends 1.3, and says to prefer 1.3 whenever both ends have it. It also says that 1.2, configured the way it describes, is as safe as 1.3: ECDHE key exchange for forward secrecy, AES-GCM, and none of the CBC-mode suites. So the real choice is between “1.2 and 1.3” and “1.3 only”. A 1.3-only service has a smaller attack surface and a shorter configuration, and it shuts out every client that speaks 1.2 at most: an older phone, a card terminal, a partner’s integration on an old Java runtime. That trade is cheap to settle with evidence. Log the negotiated version for a few weeks, then decide on the numbers rather than on what sounds modern.
Termination second. Wherever TLS ends, the private key lives there and the traffic turns back into plain text. There are three common places for it:
- A CDN or a cloud load balancer. The provider holds the key, or a certificate it issued itself, absorbs the handshake load, and sees every request in the clear. The hop from there to the origin needs TLS of its own, or it crosses the network unencrypted.
- A reverse proxy on the host, such as Nginx, Apache, HAProxy or Caddy. One process holds the keys for every site on the machine and passes plain requests to the applications behind it over loopback or a local socket.
- The application itself. Each service holds its own key. That suits traffic between services and mutual TLS, where the client presents a certificate as well, and it multiplies the places where a certificate has to be renewed.
Most estates mix all three. What counts is that someone can say, for each hop, where the plain text is and who could read it.
What it looks like in practice
The company’s own production host shows how little TLS termination needs. It is a single-core cloud instance with 512 MB of memory, about 464 MB of it usable, and 10 GB of disk. On it the web server terminates TLS for seven domains alongside the git forge, a Tor onion service, a Gemini server, a Gopher server, encrypted backups and intrusion banning. That is termination on the host at its plainest: one web server answers for all seven sites, and their encryption costs no extra machine and no extra service. The design treated memory as the binding constraint, and everything on that list, termination included, runs inside those 464 MB.
The rule the company works to is written on its security page: encryption in transit everywhere, with certificates issued and renewed automatically and the renewal monitored. The reason given there is that TLS most often fails through a certificate nobody was watching. The failures below are the ones left once renewal runs by itself.
Where it goes wrong
Some clients drop with a handshake failure while browsers connect fine. The two ends share no version or cipher suite. Someone narrowed the server to 1.3 only, or to a short list of suites, and an older client offers nothing on it; or a server still offering only CBC suites meets a client that dropped them. The alert says “handshake failure” or “protocol version” and little else. Test each version from outside:
openssl s_client -connect example.com:443 \
-servername example.com -tls1_2 </dev/null
Log the negotiated version and suite before narrowing anything, and keep 1.2 with ECDHE and AES-GCM next to 1.3 until the logs show that nobody uses it.
Browsers trust the certificate, and scripts, curl and mobile apps reject it.
The server sends its own certificate without the intermediate that links it to
the root. Browsers often fill the gap from intermediates they have met before
or fetch the missing one themselves; most TLS libraries do neither and report
an unknown issuer. Configure the full chain file, the server’s certificate
first and then the intermediates, and test with a client that keeps no cache:
the openssl s_client line above prints the chain the server actually sent.
Certificate checking is switched off “for now”, and the setting ships. A
test server with a self-signed certificate, or a corporate proxy that re-signs
traffic, and someone adds verify=False, curl -k, InsecureSkipVerify or
NODE_TLS_REJECT_UNAUTHORIZED=0. The connection stays encrypted, to whoever
answers it: anyone in between can present a certificate of their own and read
everything. Put the right CA certificate in the client’s trust store, or point
the client at it explicitly, and make CI fail on those flags.
Some visitors get a certificate for a different site. The server presents its default certificate whenever a ClientHello names a host it has no configuration for, or names none at all. That happens when a new domain is pointed at the address before its server block exists, and when a monitoring probe or an old client sends no SNI. The browser shows a name mismatch, and on a shared host it also tells the visitor which other sites live there. Give every public name its own block and a certificate that covers it, and make the default block refuse the handshake rather than offer a stranger’s certificate.
A payment, or any other request, is carried out twice. The terminator accepts 0-RTT early data, a POST arrives inside it, and an attacker or a network retry sends the same bytes again on a new connection. RFC 8446 lets a server accept early data and leaves replay between connections for the application to handle. Accept early data only for requests that are safe to repeat, or have the terminator refuse it for anything else; RFC 8470 defines the 425 Too Early status for exactly that hand-off.
Further reading
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 — the handshake, the record layer, and the appendix on why 0-RTT data can be replayed.
- RFC 9325: Recommendations for Secure Use of TLS and DTLS — BCP 195: which versions to allow, which 1.2 cipher suites to keep, and the rules for SNI.
- RFC 8996: Deprecating TLS 1.0 and TLS 1.1 — the formal end of the two older versions and the reasons behind it.
- RFC 8470: Using Early Data in HTTP — how a terminator and a server agree on which requests may travel in 0-RTT, and the 425 status.