TLS
In one line
Section titled “In one line”TLS (Transport Layer Security) turns a readable, forgeable byte stream into a channel that is encrypted, tamper-evident and tied to a proven identity. It does this with one handshake that agrees on keys and checks a certificate.
Why it exists
Section titled “Why it exists”TCP and IP deliver bytes in the clear. Anyone on the path (Wi-Fi, ISP, a compromised router) can read them, change them, or pretend to be the server.
- SSL came from Netscape in the mid 1990s to protect HTTP. SSL 2.0 and SSL 3.0 both proved breakable. RFC 6176 (2011) prohibits SSL 2.0 and RFC 7568 (2015) deprecates SSL 3.0.
- TLS 1.0 (RFC 2246, January 1999) was the IETF’s standardised successor. TLS 1.1 followed in April 2006 and TLS 1.2 (RFC 5246) in August 2008. TLS 1.2 added AEAD ciphers and SHA-256.
- Years of attacks on old options (CBC modes, RC4, compression, static RSA, export ciphers, renegotiation) showed that the protocol carried too many choices.
- TLS 1.3 (RFC 8446, August 2018) removed the dangerous options instead of patching them, encrypted most of the handshake, and cut it to one round trip. RFC 9846 (July 2026) is the current edition of TLS 1.3 and replaces RFC 8446.
- RFC 8996 (March 2021) formally deprecated TLS 1.0 and 1.1: they depend on SHA-1 and lack AEAD ciphers.
What TLS gives you:
| Property | Meaning | Mechanism in TLS 1.3 |
|---|---|---|
| Confidentiality | nobody on the path can read the data | AEAD cipher (AES-GCM or ChaCha20-Poly1305) with keys from an ephemeral key exchange |
| Integrity | nobody can change, drop or reorder data unnoticed | the AEAD authentication tag on every record, and sequence numbers |
| Authentication | you talk to the holder of the private key for that name | X.509 certificate chain plus a signature over the handshake |
What it does not give you: it hides neither the IP addresses nor the sizes and timing of traffic, and without ECH it does not hide the server name.
How it works
Section titled “How it works”The TLS 1.3 handshake
Section titled “The TLS 1.3 handshake”One round trip. It runs after the TCP handshake, or inside the QUIC handshake for HTTP/3.
- ClientHello (clear text). The client sends:
supported_versions: TLS 1.3 (the old version field stays at 1.2 so middleboxes do not choke);- cipher suites it accepts. TLS 1.3 has only five, all AEAD, for example
TLS_AES_128_GCM_SHA256; supported_groupsand akey_share: an ephemeral public key for the group it guesses the server will pick, usually X25519 or P-256;signature_algorithms;server_name(SNI, Server Name Indication): the host it wants;- ALPN (Application-Layer Protocol Negotiation):
h2,http/1.1; - a random value.
- ServerHello (clear text). The server picks the version, one cipher suite and one group, and sends its own ephemeral
key_share. If the client guessed the wrong group, the server answers with HelloRetryRequest and the handshake costs one more round trip. - Key agreement, ECDHE. Each side combines its private key with the other’s public key (Elliptic Curve Diffie-Hellman, ephemeral). Both arrive at the same shared secret. It never crosses the wire, and the ephemeral keys are thrown away afterwards.
- Key schedule, HKDF. HKDF (HMAC-based key derivation function) stretches secrets in stages: Early Secret (from a pre-shared key, or zeros), Handshake Secret (mixes in the ECDHE result), Master Secret. Each stage is bound to a hash of all handshake messages so far (the transcript). Separate keys come out for each direction and for handshake and application traffic.
- EncryptedExtensions. From here everything is encrypted with the handshake keys. This message carries the ALPN result and other extensions that used to be visible.
- Certificate. The server sends its leaf certificate and the intermediates. With mTLS it also sends a CertificateRequest first.
- CertificateVerify. The server signs the transcript hash with the private key of its certificate. This proves it holds the key, and binds the certificate to this exact handshake. RSA signatures must use RSA-PSS.
- Finished (server). An HMAC over the whole transcript, keyed from the handshake secret. Any tampering with earlier messages, such as stripping strong ciphers, makes it fail.
- Client verification. The client checks the chain up to a trusted root, the name against the SAN, the dates, revocation and CT data, and the CertificateVerify signature. Then it sends its own Finished, and its first request in the same flight.
- Application data. Both sides switch to application traffic keys. Each record holds at most 2^14 bytes of plaintext. Keys can be rotated mid-connection with KeyUpdate.
- NewSessionTicket. After the handshake the server sends one or more tickets for later resumption.
Certificates and chains
Section titled “Certificates and chains”A certificate is an X.509 document that binds a public key to names, signed by a certificate authority (CA).
- Root CA. Self-signed, lives in the trust store of the OS or browser, key kept offline.
- Intermediate CA. Signed by the root. Does the daily signing, so the root key stays offline and an intermediate can be revoked.
- Leaf. Your certificate. Signed by an intermediate.
The server must send the leaf and the intermediates. The client already has the root. A missing intermediate is the classic “works in my browser, fails in curl” bug, because browsers may cache or fetch intermediates and other clients do not.
What the client checks, in order:
- Each signature in the chain, up to a root in its trust store.
- Name. The requested host must match a
subjectAltName(SAN) entry. A wildcard*.example.comcovers one label only:a.example.com, nota.b.example.comand notexample.com. The Common Name field is not used for this any more (RFC 9525). - Validity. Now must be between
notBeforeandnotAfter. A wrong client clock fails this. - Key usage and extended key usage (
serverAuth, orclientAuthfor mTLS). - Revocation and Certificate Transparency, depending on the client.
Validity periods are shrinking. The CA/Browser Forum Baseline Requirements (ballot SC-081) cap public TLS certificates at:
| Issued on or after | Maximum validity |
|---|---|
| before 15 March 2026 | 398 days |
| 15 March 2026 | 200 days |
| 15 March 2027 | 100 days |
| 15 March 2029 | 47 days |
Let’s Encrypt issues 90 day certificates by default, offers 6 day certificates, and plans a 64 day default in 2027 and 45 days in 2028. Manual renewal is no longer realistic. Automate it.
Certificate Transparency (CT). Every publicly trusted certificate is submitted to public append-only logs. The log returns a Signed Certificate Timestamp (SCT), which is embedded in the certificate. Chrome rejects certificates without enough SCTs: from at least 2 logs for certificates valid up to 180 days, 3 for longer ones. The effect for you: every certificate issued for your domains is public and searchable, so you can detect misissuance, and so can attackers enumerate your hostnames.
Revocation. See the table in Compare. In practice revocation is weak, which is why lifetimes are getting shorter. Let’s Encrypt removed OCSP URLs from its certificates in May 2025 and shut down its OCSP responders in August 2025. It publishes CRLs only.
ACME (Automatic Certificate Management Environment, RFC 8555, March 2019) automates issuance:
- The client creates an account key and submits an order for a list of names.
- The CA returns one authorization per name, each with challenges.
- The client proves control of the name by answering one challenge.
- The client sends a CSR (certificate signing request) to the order’s finalize URL.
- The CA issues, and the client downloads the certificate and chain.
Before issuing, the CA also checks the domain’s CAA record. See DNS.
Mutual TLS: the client presents a certificate too.
- The server adds CertificateRequest to its flight.
- The client answers with its own Certificate and CertificateVerify before Finished.
- The server validates that chain against the CA bundle it trusts for clients. This is usually a private CA, not the public roots.
The identity is whatever the certificate carries, usually a SAN: a DNS name, or a URI such as a SPIFFE ID in service meshes. mTLS authenticates the peer. Deciding what that peer may do is a separate authorization step.
Where you meet it: service to service traffic in a mesh, the Kubernetes API server and kubelets, database client certificates, partner APIs.
SNI and ECH
Section titled “SNI and ECH”One IP address serves many names, so the server must know which certificate to present before anything is encrypted. SNI carries the host name in the ClientHello, in clear text. Anyone on the path can read it, and networks filter on it.
ECH (Encrypted Client Hello, RFC 9849, March 2026) closes that gap:
- The site publishes an ECH configuration with a public key in its HTTPS DNS record.
- The client builds the real ClientHello (the inner one, with the true SNI and ALPN) and encrypts it to that key with HPKE (Hybrid Public Key Encryption).
- It wraps the result in an outer ClientHello whose SNI is a shared public name of the provider.
- The client-facing server decrypts the inner hello and continues the handshake with it.
Observers see the provider’s public name and the IP address. ECH hides little unless many sites share that provider, and the DNS lookup must be encrypted too, or the name leaks there.
Session resumption and 0-RTT
Section titled “Session resumption and 0-RTT”A full handshake costs a signature, a chain validation and a round trip. Resumption skips the certificate part.
- TLS 1.2 had two mechanisms: session IDs (the server keeps state) and session tickets (the server encrypts the state and the client stores it).
- TLS 1.3 has one: after the handshake the server sends a NewSessionTicket. The client later presents it as a pre-shared key (PSK). A ticket lives at most 7 days (604800 seconds). With the
psk_dhe_kemode a fresh ECDHE exchange still runs, so the resumed session keeps forward secrecy. - A resumed TLS 1.3 handshake is still one round trip, with no certificate and no signature.
0-RTT (early data). With a PSK the client can send application data in its very first flight, encrypted with a key derived from the PSK alone. Two costs:
- No forward secrecy for the early data. It depends only on the ticket key.
- Replay. The server has not contributed anything fresh, so an attacker who copies the first flight can send it again and the server may process it twice.
Rules that follow:
- Send only requests that are safe to repeat, such as
GETwithout side effects. Never a paymentPOST. - Servers defend with single-use tickets, a record of recent ClientHellos, or a freshness window. None of these is complete across a fleet of servers.
- In HTTP, a proxy that forwards early data adds the header
Early-Data: 1. An origin that does not want to risk it answers425 Too Early, and the client retries after the handshake (RFC 8470).
Compare
Section titled “Compare”Versions
Section titled “Versions”| SSL 3.0 | TLS 1.0 | TLS 1.1 | TLS 1.2 | TLS 1.3 | |
|---|---|---|---|---|---|
| Published | 1996 | January 1999 | April 2006 | August 2008 | August 2018 |
| Status | deprecated, RFC 7568 (2015) | deprecated, RFC 8996 (2021) | deprecated, RFC 8996 (2021) | in use, obsoleted as a specification by RFC 9846 | current |
| Round trips, full handshake | 2 | 2 | 2 | 2 | 1 |
| Round trips, resumed | 1 | 1 | 1 | 1 | 1, or 0 with early data |
| Key exchange | static RSA, DH, DHE | static RSA, DHE, ECDHE by extension | static RSA, DHE, ECDHE by extension | static RSA, DHE, ECDHE | ECDHE or DHE only, always ephemeral |
| Forward secrecy | optional | optional | optional | optional, depends on the suite | always |
| Record protection | MAC-then-encrypt, RC4 or CBC | RC4 or CBC | RC4 or CBC | CBC, or AEAD (GCM, ChaCha20) | AEAD only |
| Handshake encrypted | no | no | no | no, the certificate is visible | yes, after ServerHello |
| Cipher suites | many | many | many | many, each naming key exchange, signature, cipher and hash | five, each naming only AEAD and hash |
What TLS 1.3 removed: static RSA and static Diffie-Hellman key exchange, CBC and RC4 ciphers, compression, renegotiation, DSA, custom DHE groups, session IDs and the old ticket scheme, and ChangeCipherSpec (kept only as a dummy message for middlebox compatibility). It also added a downgrade signal: a TLS 1.3 server forced down to 1.2 writes a fixed marker into the last 8 bytes of its random value, and a TLS 1.3 client aborts when it sees it.
Revocation
Section titled “Revocation”| Method | How it works | Cost |
|---|---|---|
| CRL (certificate revocation list) | the CA publishes a signed list of revoked serial numbers; clients or browser vendors download it | lists grow large; freshness depends on download schedule |
| OCSP | the client asks the CA’s responder about one certificate | extra round trip, leaks which site you visit to the CA, clients usually ignore a failed lookup |
| OCSP stapling | the server fetches the OCSP response and sends it in the handshake | server must refresh it; only helps if the client insists on it |
| OCSP Must-Staple | a certificate extension that makes a missing staple fatal | brittle when the responder is down; Let’s Encrypt stopped issuing it in 2025 |
| Short-lived certificates | the certificate expires before revocation would matter | needs fully automated renewal |
ACME challenges
Section titled “ACME challenges”| Challenge | Proof | Port | Wildcards | Pick it when |
|---|---|---|---|---|
http-01 |
serve a token at /.well-known/acme-challenge/ |
80 only | no | one web server, port 80 reachable |
dns-01 |
publish a TXT record at _acme-challenge.<name> |
none | yes | wildcards, hosts that are not public, many servers; needs DNS API credentials |
tls-alpn-01 |
answer a TLS handshake with a special ALPN and certificate | 443 | no | port 80 closed; mostly used by TLS-terminating proxies |
Where TLS ends
Section titled “Where TLS ends”| Mode | What the proxy does | Proxy sees plaintext | Back end sees | Pick it when | Cost |
|---|---|---|---|---|---|
| Termination (offload) | ends TLS, forwards plain HTTP | yes | plain HTTP, client identity only in headers | trusted private network, need L7 routing, WAF, caching | traffic behind the proxy is readable |
| Re-encryption (bridging) | ends TLS, opens a new TLS connection to the back end | yes | TLS from the proxy | L7 features plus encryption in transit everywhere | two handshakes, certificates on both hops |
| Passthrough | forwards the TCP stream, may route on the clear text SNI | no | the client’s own TLS session | end-to-end encryption, client certificates must reach the app | no L7 routing, no header injection, no WAF |
| Mesh mTLS | sidecar or node proxy on each side does mTLS between workloads | only the local proxy | plain traffic from its local proxy | zero-trust between services without changing apps | a private CA, rotation, more moving parts |
What breaks
Section titled “What breaks”| Symptom | Cause | Check |
|---|---|---|
certificate has expired |
renewal did not run, or the new file was not reloaded | openssl s_client then -dates (below) |
certificate is not yet valid |
client clock is wrong | date -u |
unable to get local issuer certificate, verify error 20 or 21 |
server does not send the intermediate, or the client lacks the root (private CA) | openssl s_client -showcerts, count the certificates |
hostname mismatch, no alternative certificate subject name matches |
the name is not in the SAN, or the proxy served its default certificate because SNI did not match | -ext subjectAltName; compare with and without -servername |
tlsv1 alert protocol version |
client and server share no TLS version | openssl s_client -tls1_2, -tls1_3 |
handshake failure, no shared cipher |
no common cipher suite or signature algorithm | openssl s_client -cipher ..., nmap --script ssl-enum-ciphers -p 443 host |
wrong version number |
the port speaks plain text, or a proxy answered with HTTP | curl -v http://host:443/ |
certificate required, bad certificate |
mTLS: the client sent no certificate, or one the server’s CA bundle does not trust | curl --cert --key, check the CA list the server offers in s_client output |
| Works in a browser, fails in curl or Java | missing intermediate, or a different trust store | openssl s_client -showcerts, curl --cacert |
| Connection reset during ClientHello | a middlebox filters on SNI | try another SNI with --resolve, compare networks |
Look at what the server presents:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/nullCertificate chain 0 s:CN = example.com i:C = US, O = Example CA, CN = Example Intermediate R1 1 s:C = US, O = Example CA, CN = Example Intermediate R1 i:C = US, O = Example CA, CN = Example Root...New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384ALPN protocol: h2Verify return code: 0 (ok)s: is the subject and i: the issuer. Each i: must equal the next s:. One entry only means the intermediate is missing.
# dates, names and issuer of the leafopenssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# short summary: version, cipher, peer certificate, verificationopenssl s_client -connect example.com:443 -servername example.com -brief </dev/null
# force a versionopenssl s_client -connect example.com:443 -tls1_2 </dev/nullopenssl s_client -connect example.com:443 -tls1_3 </dev/null
# is an OCSP response stapledopenssl s_client -connect example.com:443 -servername example.com -status </dev/null | grep -A3 'OCSP response'
# verify a chain from filesopenssl verify -CAfile root.pem -untrusted intermediate.pem leaf.pem
# does the private key match the certificate (compare the two hashes)openssl x509 -in leaf.pem -noout -pubkey | openssl sha256openssl pkey -in key.pem -pubout | openssl sha256With curl:
curl -v https://example.com/ 2>&1 | grep -E 'SSL connection|ALPN|subject|issuer|expire'curl --tlsv1.2 --tls-max 1.2 -sI https://example.com/ # TLS 1.2 onlycurl --resolve example.com:443:203.0.113.7 https://example.com/ # test one back end with the right SNIcurl --cacert ca.pem --cert client.pem --key client.key https://api.internal.example/ # mTLSKeywords
Section titled “Keywords”TLS, SSL, TLS 1.3, TLS 1.2, RFC 8446, RFC 9846, handshake, ClientHello, ServerHello, HelloRetryRequest, key_share, supported_groups, X25519, P-256, ECDHE, Diffie-Hellman, forward secrecy, HKDF, key schedule, transcript hash, EncryptedExtensions, Certificate, CertificateVerify, Finished, AEAD, AES-GCM, ChaCha20-Poly1305, cipher suite, SNI, ALPN, ECH, HPKE, X.509, root CA, intermediate CA, leaf certificate, chain, trust store, SAN, wildcard certificate, validity, 47 days, Certificate Transparency, SCT, CRL, OCSP, OCSP stapling, Must-Staple, ACME, http-01, dns-01, tls-alpn-01, CAA, CSR, mTLS, client certificate, SPIFFE, session resumption, session ticket, PSK, NewSessionTicket, 0-RTT, early data, replay, 425 Too Early, TLS termination, passthrough, re-encryption, downgrade protection, openssl s_client.
Sources
Section titled “Sources”- RFC 9846: TLS 1.3 (current edition)
- RFC 8446: TLS 1.3 (2018)
- RFC 5246: TLS 1.2
- RFC 8996: Deprecating TLS 1.0 and TLS 1.1
- RFC 7568: Deprecating SSL 3.0
- RFC 6176: Prohibiting SSL 2.0
- RFC 5280: X.509 certificate and CRL profile
- RFC 9525: Service identity in TLS
- RFC 6066: TLS extensions, SNI and certificate status request
- RFC 7301: ALPN
- RFC 9849: TLS Encrypted Client Hello
- RFC 8470: Using early data in HTTP
- RFC 6960: OCSP
- RFC 6962: Certificate Transparency
- RFC 8555: ACME
- CA/Browser Forum: Ballot SC-081v3, schedule of reducing validity
- Chrome Certificate Transparency policy
- Let’s Encrypt: Challenge types
- Let’s Encrypt: Ending OCSP support in 2025
- Let’s Encrypt: Certificate lifetime plans
- OpenSSL: s_client manual
- curl manual