# TLS

## 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](/portfolio/ran-the-whole-company-on-one-512mb-host-109/)
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](/security/): 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:

```sh
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](https://www.rfc-editor.org/rfc/rfc8446)
  — 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](https://www.rfc-editor.org/rfc/rfc9325)
  — 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](https://www.rfc-editor.org/rfc/rfc8996)
  — the formal end of the two older versions and the reasons behind it.
- [RFC 8470: Using Early Data in HTTP](https://www.rfc-editor.org/rfc/rfc8470) —
  how a terminator and a server agree on which requests may travel in 0-RTT,
  and the 425 status.

<https://engineer.company/glossary/tls/>
