Skip to content
yutils

How TLS Handshake Actually Works

What happens between ClientHello and the first encrypted byte — certificates, cipher suites, key exchange, SNI, and why TLS 1.3 dropped the handshake from two round trips to one.

~9 min read

Behind the padlock in your address bar sits a TLS handshake. Between ClientHello and the first encrypted byte, two computers negotiate certificates, cipher choices, and a shared secret nobody else can derive. How does that work — especially the part where two strangers create a shared key in front of eavesdroppers? This guide walks through TLS 1.2 and 1.3 handshakes, certificate chains, SNI, and forward secrecy.

Three problems TLS solves

  • Confidentiality — no one can read the traffic (encryption)
  • Integrity — no one can tamper with the traffic (MAC / AEAD)
  • Authenticity — the server is who they claim (certificates)

Plain HTTP has none of these. On café Wi-Fi, anyone can read or modify your traffic.

TLS 1.2 handshake — two round trips

Client                              Server
  │                                    │
  │──── ClientHello ──────────────────→│   (1st RTT)
  │     · TLS version (1.2)            │
  │     · proposed cipher suites       │
  │     · client random (28 bytes)     │
  │     · SNI (server name)            │
  │                                    │
  │←─── ServerHello ───────────────────│
  │     · chosen cipher                │
  │     · server random                │
  │←─── Certificate ───────────────────│
  │     · server certificate chain     │
  │←─── ServerKeyExchange (DHE etc) ───│
  │←─── ServerHelloDone ───────────────│
  │                                    │
  │── 1) verify certificate            │
  │── 2) generate pre-master secret    │
  │── 3) encrypt with server pubkey    │
  │                                    │
  │──── ClientKeyExchange ────────────→│   (2nd RTT)
  │──── ChangeCipherSpec ─────────────→│
  │──── Finished (encrypted) ─────────→│
  │                                    │
  │←─── ChangeCipherSpec ──────────────│
  │←─── Finished (encrypted) ──────────│
  │                                    │
  │←──── Application Data (encrypted) ─→│   (real traffic begins)

Two RTTs. At 50ms Tokyo↔Seoul that's 100ms; at 180ms US↔Korea that's 360ms before any HTTP payload moves. Painful on mobile or satellite links.

TLS 1.3 handshake — one round trip

Client                              Server
  │                                    │
  │──── ClientHello ──────────────────→│   (1st RTT)
  │     · TLS version (1.3)            │
  │     · key share (DHE/ECDHE) up-front│
  │     · supported cipher suites      │
  │     · SNI                          │
  │                                    │
  │←─── ServerHello ───────────────────│
  │     · chosen cipher                │
  │     · server key share             │
  │     · Certificate (encrypted)      │
  │     · CertificateVerify (encrypted)│
  │     · Finished (encrypted)         │
  │                                    │
  │── verify certificate              │
  │                                    │
  │──── Finished (encrypted) ─────────→│
  │←──── Application Data (encrypted) ─→│   (immediate)

Key differences:

  • 1 RTT — the client puts its key share in the first ClientHello
  • 0-RTT resumption — for repeat connections, reuse a previous session ticket → zero handshake RTT (GET only, due to replay risk)
  • Simpler ciphers — TLS 1.2's 30+ cipher choices collapse to ~5 (AEAD families)
  • Forward secrecy mandatory — RSA key exchange removed; only ephemeral DH

Cipher suites — what's actually being chosen

TLS 1.2 format:
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    │     │   │    │       │    │
    │     │   │    │       │    └─ MAC algorithm
    │     │   │    │       └────── AEAD mode
    │     │   │    └────────────── symmetric cipher
    │     │   └─────────────────── authentication algorithm (RSA signature)
    │     └─────────────────────── key exchange (ECDHE)
    └───────────────────────────── protocol

TLS 1.3 format (simplified):
TLS_AES_256_GCM_SHA384
    │     │       │
    │     │       └─ hash
    │     └───────── AEAD
    └─────────────── protocol

TLS 1.3's cipher suite is simpler because key exchange and authentication are negotiated separately (key_share / signature_algorithms extensions). Far fewer combinations to misconfigure.

Certificates — who vouches for whom

The server presents an X.509 certificate:

Subject:        CN=example.com (or SAN: example.com, *.example.com)
Issuer:         Let's Encrypt R3
Valid:          2026-05-01 ~ 2026-08-01 (90 days)
Public Key:     -----BEGIN PUBLIC KEY----- ... (RSA 2048 or ECDSA P-256)
Signature:      signed by Issuer's private key
Extensions:     SAN, OCSP, CT log, etc.

The client verifies:

  1. Hostname (CN or SAN) matches the requested domain
  2. Within validity period
  3. Issuer's signature is valid (requires Issuer's public key)
  4. Issuer is a trusted CA

Certificate chain — back to a trusted root

example.com cert         ← sent by server
   ↑ signed by
Let's Encrypt R3 (intermediate CA)   ← server also sends this
   ↑ signed by
ISRG Root X1 (root CA)               ← preinstalled in OS / browser

OS and browsers ship trust stores with ~200 root CA public keys. Mozilla / Apple / Microsoft update these annually.

SNI — many sites per IP

Without SNI:
Client → 203.0.113.5:443 → "Hello"
Server: which site's cert should I send?

With SNI:
Client → 203.0.113.5:443 → "Hello, host=example.com"
Server: pick example.com's cert and send

The SNI extension in ClientHello. Required for Cloudflare / AWS / any shared host. But it's plaintext — your ISP can see which site you're visiting.

ESNI / ECH (Encrypted Client Hello) — TLS 1.3 encrypts the SNI too. Cloudflare has it on by default. Slowly rolling out.

Forward secrecy — protecting yesterday's traffic

Old RSA key exchange — if the server's private key ever leaks, all past recorded traffic can be decrypted. A massive risk for long-running services.

Forward secrecy with ephemeral keys — each handshake creates a fresh, throwaway key. Lose the long-term private key, and yesterday's traffic stays secret.

DHE  (Diffie-Hellman Ephemeral)
ECDHE (Elliptic Curve DHE) — current standard, faster

How it works:
1. Client and server each generate ephemeral key pairs
2. Exchange the public halves
3. Both derive the same shared secret (DH math — eavesdroppers can't)
4. Throw away the ephemeral private keys after handshake

TLS 1.3 mandates ECDHE — RSA key exchange is gone.

Mutual TLS (mTLS) — the server checks you too

Regular TLS:
- client verifies server cert

mTLS:
- server requests a client cert
- both sides hold a cert + private key
- mutual authentication

Where it's used:

  • Service-to-service auth (Kubernetes service meshes)
  • API gateway client auth (instead of passwords)
  • Banking / financial APIs

HSTS — preventing HTTP downgrade

Response header:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

→ Browser enforces HTTPS for this domain for one year
→ http://example.com auto-rewrites to https:// in the browser
→ Cert errors can't be clicked through

Preload — your domain is hardcoded into the Chrome / Firefox HSTS list. The strongest form. Hard to leave once submitted.

Common pitfalls

1. Expired certificates

Let's Encrypt issues 90-day certs. Automated renewal (certbot, Caddy) is essential — a missed cron job equals an outage.

2. Mixed content

<!-- inside an HTTPS page -->
<img src="http://insecure.com/logo.png">
→ Browser blocks or warns (mixed content)

3. Weak cipher suites

If the server still allows TLS 1.0 / 1.1 / 3DES / RC4, it's vulnerable. Audit with nmap --script ssl-enum-ciphers -p 443 example.com or SSL Labs.

4. Missing SNI

curl, old Java, Python 2 — requests without SNI hit the default vhost. The cert sent doesn't match → handshake fails.

5. Missing intermediate certificates

If the server omits the intermediate CA from the chain, clients without auto-fetch (common outside browsers) fail validation.

References

Summary

  • TLS gives confidentiality, integrity, authenticity. Plain HTTP gives none.
  • TLS 1.2 = 2 RTT handshake. TLS 1.3 = 1 RTT + optional 0-RTT resumption.
  • Cipher suite = key exchange · authentication · AEAD · hash combination. TLS 1.3 simplified the menu.
  • Certificate chain — server cert → intermediate CA → root CA (in the OS / browser trust store).
  • SNI tells the server which vhost. Plaintext; ECH is the encrypted successor.
  • Forward secrecy via ephemeral keys → past traffic stays safe even if the long-term private key leaks. TLS 1.3 mandates it.
  • mTLS = mutual auth. Used in service meshes and API gateways.
  • HSTS forces HTTPS. Preload list is the strongest version.
  • Try it: HMAC Generator to play with the MAC algorithms TLS uses internally; cURL Builder + curl -v for wire traces.
Back to guides