Server

The TLS 1.3 Handshake, Walked Through

By Prabath Thalangama· October 24, 2026· 3 min read
#tls#security#networking

Introduction

TLS 1.3 (RFC 8446, 2018) is now the default for most traffic. It's faster — one round trip instead of two — and drops every cipher and feature that caused a decade of vulnerabilities. Knowing the flow helps when a handshake fails or a scanner flags your config.

Concepts

TLS does three jobs: authentication (you're really talking to example.com), key exchange (agree on a shared secret nobody watching can derive), and encryption (protect the data). TLS 1.3 does key exchange with ephemeral Diffie-Hellman only, so every session has forward secrecy — capturing today's traffic and stealing the server key next year still doesn't decrypt it.

The flow

Client                                             Server

ClientHello
  + supported_versions: [TLS 1.3]
  + key_share: client's DH public key (guessed group)
  + signature_algorithms, cipher_suites, SNI
-------------------------------------------------->
                                          ServerHello
                              + key_share: server's DH public key
                              + selected cipher suite
                    {EncryptedExtensions}
                    {Certificate}          <- now encrypted
                    {CertificateVerify}    <- signs the handshake
                    {Finished}
<--------------------------------------------------
{Finished}
-------------------------------------------------->
[Application Data flows both ways]

Braces {} mean "encrypted with the handshake key" — in 1.3 the certificate itself is encrypted, so a passive observer can't see which site you're visiting from the cert (SNI is still cleartext unless Encrypted Client Hello is used).

Key points:

  • The client guesses a DH group and sends its key share in the first message. If the server likes the guess, that's it — one round trip. If not, the server sends HelloRetryRequest asking for a different group (extra round trip, uncommon).
  • CertificateVerify is the server signing a transcript of everything so far with its private key — that's the actual proof of identity.
  • Both sides send Finished (a MAC over the transcript) to confirm nothing was tampered with.

What 1.3 removed from 1.2

  • RSA key exchange (no forward secrecy), static DH.
  • CBC-mode ciphers, RC4, 3DES, SHA-1.
  • Renegotiation, compression (CRIME), custom DH groups (Logjam).
  • The separate ChangeCipherSpec step (kept only as a dummy for middlebox compatibility).

0-RTT resumption

On a repeat visit the client can send application data in the very first packet, encrypted with a key from the previous session (a PSK). It's fast but replayable — a network attacker can resend that data. Use it only for idempotent requests (GET), never for anything that changes state.

Hands-on

# Force 1.3 and show the negotiated parameters
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>/dev/null \
  | grep -E "Protocol|Cipher|Server Temp Key"

# Full handshake trace
openssl s_client -connect example.com:443 -servername example.com -trace </dev/null

# Which versions/ciphers does the server actually support?
nmap --script ssl-enum-ciphers -p 443 example.com

Server Temp Key: X25519, 253 bits confirms ephemeral ECDH (forward secrecy).

Verification and troubleshooting

  • handshake failure / no protocols available — the client offers only TLS 1.3 and the server maxes out at 1.2 (or vice versa for an old client). Check both ends' min/max version.
  • certificate verify failed: unable to get local issuer certificate — the server isn't sending the intermediate. Serve fullchain.pem, not just the leaf. Verify with the SSL/TLS checker or Certificate Decoder.
  • Handshake works in a browser, fails from curl/an app — SNI. The client isn't sending the servername, so the server returns the default vhost's cert. curl does this right; some libraries need it set explicitly.
  • HelloRetryRequest every time (slow) — the server's preferred group isn't in the client's first key_share. Usually harmless; if it's a hot path, align the client's default group with the server's.
  • Scanner says "weak / deprecated" — you still allow TLS 1.0/1.1 or CBC ciphers. Set ssl_protocols TLSv1.2 TLSv1.3; and a modern cipher list.
  • 0-RTT causing duplicate side effects — disable early_data for non-idempotent endpoints.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.