Every time you load an HTTPS page, a TLS handshake happens in milliseconds. Most people treat it as a black box — it works or it doesn’t. But understanding what actually happens is useful when you’re debugging TLS errors, optimizing performance, or configuring cipher suites.
The Purpose of the Handshake
The handshake does three things:
- Authenticates the server (and optionally the client) using certificates
- Negotiates the protocol version and cipher suite both sides will use
- Establishes the session keys used for the encrypted connection
Only step 3 actually encrypts the data. The handshake itself happens mostly in plaintext — but in a way that prevents eavesdroppers from deriving the session keys.
TLS 1.2 Handshake — Full Flow
Here’s what happens in a TLS 1.2 connection:
Client Hello — the client sends:
- TLS version it supports (e.g., TLS 1.2)
- A random 32-byte value (
ClientRandom) - A list of supported cipher suites (e.g., TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
- A list of supported compression methods (TLS 1.3 removes compression)
- Extensions: SNI (server name), supported groups, signature algorithms, etc.
Server Hello — the server responds:
- TLS version to use
- A random 32-byte value (
ServerRandom) - The chosen cipher suite
- A session ID (for session resumption)
Certificate — the server sends its certificate chain (leaf + intermediates).
Server Key Exchange — for ECDHE/DHE cipher suites, the server sends its key exchange parameters and signs them with its private key. This is how forward secrecy works — the session key is derived from ephemeral parameters, not the server’s long-term private key.
Server Hello Done — signals the end of the server’s hello messages.
Client Key Exchange — the client sends its contribution to the key exchange. For ECDHE, this is the client’s ephemeral public key.
At this point, both sides have enough information to derive the master secret and session keys. They derive the same keys independently — no keys are transmitted directly.
Change Cipher Spec + Finished — both sides send these to signal they’re switching to encrypted communication. The Finished message is the first encrypted message and contains a hash of the entire handshake, which verifies that neither side tampered with the handshake.
TLS 1.3 Handshake — Faster and Cleaner
TLS 1.3 redesigned the handshake for performance and security:
Client Hello — same as TLS 1.2, but the client includes key exchange data (key shares for several groups) in the hello itself, not in a separate message.
Server Hello + Certificate + Finished — the server responds with its choice, sends the certificate, and its Finished message in what’s essentially one round trip.
That’s it — one round trip (1-RTT) versus TLS 1.2’s two round trips (2-RTT).
TLS 1.3 also allows 0-RTT for session resumption (data sent on the first packet without waiting for the handshake), though this has replay attack tradeoffs.
Weak cipher suites, RSA key exchange, and compression are gone from TLS 1.3. The supported cipher suites are all AEAD (Authenticated Encryption with Associated Data): AES-GCM and CHACHA20-POLY1305.
Observing the Handshake
Watch a TLS handshake with openssl:
openssl s_client -connect example.com:443 -servername example.com -msg 2>&1 | head -60
The -msg flag shows message types. You’ll see ClientHello, ServerHello, Certificate, etc.
More detail with tls1_2 flag:
openssl s_client -connect example.com:443 -tls1_2 -servername example.com 2>&1 | grep -E "Protocol|Cipher"
Certificate Verification During Handshake
When the server sends its certificate, the client:
- Checks the certificate is not expired
- Checks the certificate’s Common Name or SANs match the hostname
- Builds the chain from the leaf certificate to a trusted root
- Verifies the signatures up the chain
- Optionally checks certificate revocation (OCSP or CRL)
If any step fails, the connection is rejected. This is why the intermediate certificate matters — without it, the chain can’t be built, and step 3 fails.
Session Resumption
Opening a full TLS handshake for every connection is expensive. Session resumption avoids it.
Session IDs (TLS 1.2 and earlier) — the server assigns a session ID during the handshake. On reconnection, the client presents the ID, the server looks it up, and they skip the key exchange and certificate exchange.
Session tickets (TLS 1.2 and earlier) — the server encrypts the session state and sends it to the client. On reconnection, the client presents the ticket. The server decrypts it and resumes without looking anything up — no server-side state needed.
Session tickets can weaken forward secrecy if the ticket encryption key is long-lived. If an attacker records traffic now and later compromises the ticket key, they can decrypt past sessions. This is why some security configurations disable session tickets (ssl_session_tickets off in Nginx).
TLS 1.3 PSK (Pre-Shared Key) — TLS 1.3’s resumption mechanism uses a pre-shared key derived from the previous session. More secure than TLS 1.2 tickets.
The Cipher Suite Negotiation
A cipher suite name like TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 encodes:
- ECDHE — key exchange algorithm (Elliptic Curve Diffie-Hellman Ephemeral)
- RSA — authentication algorithm (verify the server’s certificate signature)
- AES_256_GCM — bulk encryption (symmetric cipher)
- SHA384 — MAC algorithm
In TLS 1.3, cipher suites are shorter: TLS_AES_256_GCM_SHA384. The key exchange is always ECDHE in TLS 1.3 and not specified in the cipher suite name.
Forward secrecy comes from the “Ephemeral” part — ECDHE generates fresh key exchange parameters for each connection. Even if the server’s private key is later compromised, past session keys can’t be derived.
ALPN: Negotiating the Application Protocol
ALPN (Application-Layer Protocol Negotiation) is a TLS extension that lets clients and servers negotiate which application protocol to use (HTTP/1.1, HTTP/2, etc.) during the TLS handshake, avoiding a round trip.
In the Client Hello, the client lists supported protocols: ["h2", "http/1.1"]. The server picks one and includes it in the Server Hello.
# Check what ALPN protocols a server supports
openssl s_client -connect example.com:443 -servername example.com -alpn h2 2>&1 | grep ALPN
If the server supports HTTP/2, you’ll see ALPN protocol: h2.
SNI: Server Name Indication
SNI is another TLS extension that lets the client indicate which hostname it’s connecting to. This allows multiple virtual hosts on the same IP to each have their own certificate.
Without SNI, a server with multiple certificates would have to guess which one to send, or use a wildcard or multi-SAN certificate. With SNI, the client says “I’m connecting to api.example.com” and the server sends the right certificate.
SNI is sent in plaintext in the Client Hello, before encryption is established. This means network observers can see which hostname you’re connecting to even over HTTPS. This is why some privacy tools (like ESNI/ECH — Encrypted Client Hello) exist to hide it.
Understanding the handshake makes TLS debugging much less mysterious. When an SSL test reports a problem or openssl s_client shows an error, you can usually trace it back to a specific handshake step. For practical debugging commands and certificate inspection, the OpenSSL verification guide covers the tools in detail.