The TLS handshake
On this page
Every new HTTPS connection starts with a short exchange called the TLS handshake, before a single byte of the web page moves. It has two jobs:
- Prove who the server is. The server shows its certificate, and proves it holds the matching private key. Certificates and Trust covers how the browser decides to believe it.
- Agree on encryption keys that nobody else knows, even though every message of the handshake itself crosses the open internet.
The ideas behind it
Two kinds of keys
The handshake involves two different kinds of keys, used in three places, and keeping them apart makes the rest of this page much easier to follow.
| The certificate’s key pair | The key exchange key pairs | The session key | |
|---|---|---|---|
| What it is | A public key, in the certificate, and its matching private key, kept on the server | A fresh key pair on each side, made just for this connection (below) | One secret key that both sides end up knowing |
| Kind of key | Asymmetric: what one key does, only the other can undo | Asymmetric | Symmetric: the same key locks and unlocks |
| How long it lasts | As long as the certificate, usually months | One handshake, then it’s thrown away | One connection, then it’s thrown away |
| What it’s for | Proving who the server is, by signing | Agreeing on the session key | Encrypting the actual traffic |
Why not use asymmetric keys for everything? Asymmetric math is much slower than symmetric, so it’s kept for small, one-off jobs like signing and never used to encrypt a whole web page [1]. The handshake uses the slow kind briefly to set up the fast kind, and the fast kind does the rest. Certificates and Trust covers how key pairs and signatures work.
Agreeing on a secret in public
The second job sounds impossible. Two computers that have never met need to end up with the same secret key, while anyone in between can read everything they send. They do it with a key exchange, and the classic way to picture it is mixing paint:
- Both sides agree, in public, on a common starting color, say yellow.
- Each side picks a secret color of its own and never shares it. The client picks red, the server picks blue.
- Each side mixes its secret color into the yellow and sends the mixture across. The client sends orange (yellow + red), and the server sends green (yellow + blue). That mixture is each side’s key share.
- Each side adds its own secret color to the mixture it received. The client adds red to the green, the server adds blue to the orange, and both end up with the same brown (yellow + red + blue).
In the real handshake, each secret color is a throwaway private key and each key share is its matching public key. That’s a second key pair on each side, separate from the certificate’s, made fresh for every connection and used only to agree on the secret [2].
Each side ends up with brown because it adds its own unmixed secret to the other side's mixture. Someone watching only ever has mixtures.
Making the brown in step 4 takes one mixture plus one unmixed secret color, and only the two ends have one of those. Someone watching saw yellow, orange, and green. If they mix the orange and green together, they get yellow + red + yellow + blue: a brown with twice as much yellow, which is the wrong color, and there’s no way to take the extra yellow back out. They could get the right brown if they could pull the red back out of the orange, but un-mixing paint is impractical, so they can’t.
In the handshake, the client’s key share rides in its very first message and the server’s in its reply, so both sides can make the brown after one exchange. The brown becomes the session key that encrypts the rest of the conversation [2].
Real TLS does this with math instead of paint, where the “un-mixing” step is what’s impractical. For years the usual method has been one called X25519 [3], and newer browsers now pair it with a second method built to resist future quantum computers [4].
The same idea with numbers
The paint stands in for math. The original version, from Diffie and Hellman in 1976, uses ordinary numbers [5], and every step lines up with a step of the paint example. “Mixing” a secret into a number means multiplying the secret number of copies of it together, then keeping only the remainder after dividing by 23. For example, mixing 6 into 5 means multiplying six 5s together, 5⁶ = 15,625, and 15,625 ÷ 23 is 679 with 8 left over, so the result is 8.
| Step | With paint | With numbers |
|---|---|---|
| 1. Agree in public | Yellow | 5, and the 23 used for remainders |
| 2. The browser’s secret | Red | 6 |
| 2. The server’s secret | Blue | 15 |
| 3. The browser’s key share, sent | Orange (yellow + red) | 5⁶ leaves 8 |
| 3. The server’s key share, sent | Green (yellow + blue) | 5¹⁵ leaves 19 |
| 4. The browser finishes | Green + red = brown | 19⁶ leaves 2 |
| 4. The server finishes | Orange + blue = brown | 8¹⁵ leaves 2 |
| What a watcher saw | Yellow, orange, green | 5, 23, 8, 19 |
| What the watcher would need | Un-mix the red out of the orange | Work out “5 to what power leaves 8?” |
Both sides end up with 2, the brown. The watcher’s question is easy here, since they can just try every power until one leaves 8. Real key exchanges use numbers hundreds of digits long, where nobody knows a practical way to answer it, and the whole method depends on that staying true [5]. X25519 does the same thing with a different kind of math (elliptic curves) that gets the same protection from much shorter numbers [3].
The post-quantum pairing is called X25519MLKEM768. It runs X25519 together with ML-KEM, a newer method designed to hold up against future quantum computers, and stays secure as long as either one does [6]. Chrome has offered it since version 131 [4], and a connection to this site from a current OpenSSL picks it.
Forward secrecy
The secret colors are made fresh for every connection and thrown away afterward. So even if someone records an encrypted conversation today and steals the server’s certificate private key next year, they still can’t decrypt the recording, because the certificate key was only ever used to sign (prove identity), never to protect the session key. That property is forward secrecy, and TLS 1.3 requires it: the older key exchange methods that didn’t provide it were removed [2].
The handshake, step by step
The TLS 1.3 handshake, message by message
A browser opening https://app.example.com sends and receives these messages [2]:
- ClientHello (client → server, not encrypted). What the client wants and supports:
- SNI (Server Name Indication): the hostname it wants, so a server hosting many sites can pick the right certificate [7].
- ALPN (Application-Layer Protocol Negotiation): which protocols it can speak on top, like
h2(HTTP/2) orhttp/1.1[8]. - The TLS versions and cipher suites (sets of encryption algorithms, covered below) it supports.
- Its key share, the orange paint, sent right away for the method it guesses the server will pick. Browsers guess the method nearly every server supports, and Chrome now guesses the pairing with the quantum-resistant method [4].
- ServerHello (server → client, not encrypted). The server’s choices from those lists, and its key share. Both sides can now compute the shared secret, and everything after this message is encrypted [2].
- EncryptedExtensions. The rest of the server’s choices, like which protocol from the ALPN list it picked.
- Certificate. The server’s certificate chain.
- CertificateVerify. A signature over the whole handshake so far, made with the certificate’s private key [2]. This is the proof that the server actually holds that key, and the reason a copied certificate is useless on its own (what a certificate proves).
- Finished (server). A checksum of every handshake message, made with the new session key so only the two real ends could produce it, so both sides can confirm nobody altered anything on the way [2].
- Finished (client). The same confirmation from the client’s side, and the client can send its HTTP request right behind it.
The Finished checksums matter because the first two messages aren’t encrypted, so someone in the middle could edit them. Picture an attacker deleting the strongest options from the ClientHello on its way past, hoping the two sides settle on something weaker. The server would answer the edited version, but the client still has the original. Their records of the handshake no longer match, so the server’s CertificateVerify signature and Finished checksum won’t check out on the client’s side, and the handshake fails with a decrypt_error alert instead of quietly carrying on [2].
The whole handshake takes one round trip, a message out and the reply back, before the request can go. If the client guessed wrong about the key exchange method, the server sends a HelloRetryRequest asking for a different one, which costs an extra round trip [2].
Step through it here, and switch to TLS 1.2 to compare:
TLS 1.2: one more round trip
TLS 1.2 is still widely supported, and its handshake takes two round trips instead of one [9]:
- The client’s first message doesn’t include its half of the key exchange, so the key exchange only finishes on the second trip.
- The certificate travels unencrypted, so anyone watching can see exactly which certificate the server sent.
- Older 1.2 setups could use a key exchange without forward secrecy, where stealing the server’s key later would unlock recorded traffic. That’s the main reason 1.3 removed those options [2].
TLS 1.0 and 1.1 are formally deprecated and shouldn’t be used at all [10].
Versions and cipher suites
A cipher suite is the set of algorithms a connection uses. In TLS 1.2, a suite’s name lists everything at once: the key exchange, how the server proves its identity, the encryption, and the hash, which is why 1.2 suite names get long. TLS 1.3 simplified it. Key exchange and signatures are negotiated separately, and the cipher suite only names the encryption and the hash, so there are just five of them, like TLS_AES_128_GCM_SHA256 and TLS_CHACHA20_POLY1305_SHA256 [2].
Coming back: resumption and 0-RTT
After a full handshake, the server can hand the client a session ticket. Next time, the client presents the ticket and the two sides skip the certificate part, because they’ve already been through it. A resumed handshake still takes one round trip, though. The server proves who it is with the key saved from last time instead of sending its Certificate and CertificateVerify, but the ClientHello still goes out and the ServerHello and Finished still come back before the request can follow [2].
With a ticket, TLS 1.3 also allows 0-RTT (“zero round trip”): the client sends its request alongside its very first message, before the handshake finishes. It’s fast, but it comes with a catch: that early data has no protection against being replayed, so someone who captures it can send it again [2]. It’s only safe for requests where doing the same thing twice is harmless, like loading a page, and never for something like “place an order.”
In a real connection
Where the handshake fits
In the OSI model, TLS sits on top of TCP (layer 4) and underneath HTTP (layer 7), which is why it doesn’t fit neatly into one layer. The TCP connection is set up first, then the TLS handshake runs over it, and only then does HTTP get to speak. So if the handshake fails, the two machines are already connected, but the browser never sends its request and the server never sends the page. One side sends an alert and both close the connection without sending anything else [2]. The one exception is 0-RTT, covered above, where a returning client sends its request before the handshake finishes.
TCP has its own handshake before TLS starts, and the page can’t arrive until the HTTP request has gone out and come back [11], [2]:
Time runs downward. Three round trips pass before the first byte of the page arrives, and everything in teal is encrypted.
Round trips are what make a first connection slow, because each one costs the full travel time to the server and back no matter how small the messages are. If a round trip takes 50 milliseconds, the page starts arriving about 150 milliseconds after the browser starts connecting. TLS 1.2 adds one more round trip, to 200, and a returning client using 0-RTT can save one.
What someone watching can still see
TLS hides the contents of the conversation, but not everything:
- The server name. SNI is in the ClientHello, which goes out before any encryption exists, so anyone on the path can see that you connected to
app.example.com(just not which page). Encrypted Client Hello (ECH) fixes this by encrypting the ClientHello with a public key the server publishes in DNS, but it needs support from both the browser and the server [12]. - The name, a second way. Before connecting, the browser looked the name up in DNS, and ordinary DNS isn’t encrypted, so anyone on the path can see that lookup too, unless the browser uses encrypted DNS such as DNS over HTTPS [13].
- IP addresses and ports, from the layers below (the OSI model).
- Sizes and timing of the encrypted traffic.
- In TLS 1.2, the certificate as well, since it isn’t encrypted there.
When the handshake fails
When something goes wrong, the side that notices sends an alert, a short message with a name and number from the TLS standard, and closes the connection [2]. Tools print the alert’s name, though the wording varies between them, and some still say “SSL” or “sslv3” for historical reasons. The common failures:
| What went wrong | What happens | What a tool like curl shows |
|---|---|---|
| No TLS version in common, like a client that only speaks TLS 1.1 | The server sends a protocol_version alert | tlsv1 alert protocol version |
| No cipher suite or key exchange method in common | The server sends a handshake_failure alert | alert handshake failure |
| The certificate doesn’t check out: expired, wrong name, or not from a trusted CA | The client refuses it and stops | certificate has expired, no alternative certificate subject name matches target host name, self signed certificate in certificate chain |
| The client didn’t send SNI | A server hosting many sites doesn’t know which one you want and sends its default certificate, which then fails the name check | A hostname mismatch |
Without SNI, for example, this site’s server (GitHub Pages) answers with its default *.github.io certificate. Certificates and Trust has more on certificate errors, and badssl.com has deliberately broken sites to try them against:
macOS / Linux
curl --tls-max 1.1 https://camzabriskie.com/ # an old TLS version
curl https://expired.badssl.com/ # an expired certificate
curl https://wrong.host.badssl.com/ # a certificate for a different name
Windows (PowerShell)
curl.exe --tls-max 1.1 https://camzabriskie.com/ # an old TLS version
curl.exe https://expired.badssl.com/ # an expired certificate
curl.exe https://wrong.host.badssl.com/ # a certificate for a different name
The first one may not get as far as the server. Recent versions of OpenSSL (3.2 and later) turn TLS 1.0 and 1.1 off by default [14], so a curl built on one refuses on your side and prints an error of its own instead of the server’s protocol_version alert. The curl that ships with macOS gets through and shows tlsv1 alert protocol version.
Going further
Mutual TLS
In normal TLS only the server proves who it is. In mutual TLS (mTLS) the server also asks the client for a certificate, and the client answers with its own Certificate and CertificateVerify messages, proving it holds that certificate’s private key the same way the server did [2]. Load balancers and TLS termination covers how that interacts with load balancers.
Watching it happen
curl -v prints the result of the handshake. This command asks for a page, throws the page away (-o), and keeps only the handshake lines from curl’s report (-v turns the report on, -s hides the progress bar):
macOS / Linux
curl -sv -o /dev/null https://camzabriskie.com/ 2>&1 \
| grep -E 'ALPN|SSL connection|Server certificate|subject:|issuer:|verify ok'
Windows (PowerShell)
curl.exe -sv -o NUL https://camzabriskie.com/ 2>&1 |
Select-String 'ALPN|SSL connection|Server certificate|subject:|issuer:|verify ok'
On Windows, type curl.exe rather than curl, since in Windows PowerShell 5.1 plain curl is an alias for a different command [15]. For this site, on a Mac, it printed:
* ALPN: curl offers h2,http/1.1
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
* subject: CN=camzabriskie.com
* issuer: C=US; O=Let's Encrypt; CN=YR1
* SSL certificate verify ok.
That’s ALPN choosing HTTP/2, TLS 1.3 with a ChaCha20 cipher suite (the exact naming varies by TLS library), and the certificate chain checking out. curl still says “SSL” because TLS replaced an older protocol called SSL and the name stuck (more on that). AEAD in the cipher’s name stands for authenticated encryption with associated data: encryption that also detects any tampering, in one step. Every TLS 1.3 cipher works that way [2], [16].
curl only reports how the handshake turned out. To see each message from the walkthrough above as it happens, openssl s_client -connect camzabriskie.com:443 -msg prints every handshake message the client sends and receives, and -trace decodes them in more detail [17].
Both of those show the handshake from the client’s side, after it has decrypted everything. To see it the way someone watching sees it, use Wireshark, a free app that records the packets crossing your network connection and decodes them. Start a capture, load a site, and type tls into the filter bar to show only TLS traffic [18]. The ClientHello is there in full, site name included, and so is the ServerHello, and everything after that shows up only as encrypted data.
Decrypting your own capture
Wireshark can also show the encrypted part, which is useful for debugging your own traffic. Browsers and curl write out their session keys to a file when the SSLKEYLOGFILE environment variable is set, and Wireshark can use that file to decrypt the capture [18]. Delete the file afterward, since anyone who has it can decrypt that traffic.
References
Show all 18 sourcesHide sources
- [1] ↩ E. Barker, “Guideline for Using Cryptographic Standards in the Federal Government: Cryptographic Mechanisms,” NIST Special Publication 800-175B Rev. 1, National Institute of Standards and Technology, Mar. 2020. Accessed: Oct. 6, 2026. [Online]. Available: https://doi.org/10.6028/NIST.SP.800-175Br1
- [2] ↩ a b c d e f g h i j k l m n o p q r E. Rescorla, “The Transport Layer Security (TLS) Protocol Version 1.3,” RFC 9846, Internet Engineering Task Force, Jul. 2026. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc9846
- [3] ↩ a b A. Langley, M. Hamburg, and S. Turner, “Elliptic Curves for Security,” RFC 7748, Internet Engineering Task Force, Jan. 2016. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc7748
- [4] ↩ a b c D. Adrian, D. Benjamin, B. Beck, and D. O'Brien, “A new path for Kyber on the web,” Google Online Security Blog, Sep. 13, 2024. Accessed: Oct. 6, 2026. [Online]. Available: https://security.googleblog.com/2024/09/a-new-path-for-kyber-on-web.html
- [5] ↩ a b W. Diffie and M. E. Hellman, “New Directions in Cryptography,” IEEE Transactions on Information Theory, vol. IT-22, no. 6, pp. 644-654, Nov. 1976. Accessed: Oct. 6, 2026. [Online]. Available: https://ee.stanford.edu/~hellman/publications/24.pdf
- [6] ↩ K. Kwiatkowski, P. Kampanakis, B. E. Westerbaan, and D. Stebila, “Post-Quantum Traditional (PQ/T) Hybrid Key Agreement Mechanisms for TLS 1.3,” RFC 10024, Internet Engineering Task Force, Aug. 2026. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc10024
- [7] ↩ D. Eastlake 3rd, “Transport Layer Security (TLS) Extensions: Extension Definitions,” RFC 6066, Internet Engineering Task Force, Jan. 2011. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc6066
- [8] ↩ S. Friedl, A. Popov, A. Langley, and E. Stephan, “Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension,” RFC 7301, Internet Engineering Task Force, Jul. 2014. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc7301
- [9] ↩ T. Dierks and E. Rescorla, “The Transport Layer Security (TLS) Protocol Version 1.2,” RFC 5246, Internet Engineering Task Force, Aug. 2008. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc5246
- [10] ↩ K. Moriarty and S. Farrell, “Deprecating TLS 1.0 and TLS 1.1,” RFC 8996, Internet Engineering Task Force, Mar. 2021. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc8996
- [11] ↩ W. Eddy, Ed., “Transmission Control Protocol (TCP),” RFC 9293, Internet Engineering Task Force, Aug. 2022. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc9293
- [12] ↩ E. Rescorla, K. Oku, N. Sullivan, and C. A. Wood, “TLS Encrypted Client Hello,” RFC 9849, Internet Engineering Task Force, Mar. 2026. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc9849
- [13] ↩ P. Hoffman and P. McManus, “DNS Queries over HTTPS (DoH),” RFC 8484, Internet Engineering Task Force, Oct. 2018. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc8484
- [14] ↩ OpenSSL Project, “SSL_CTX_set_security_level,” OpenSSL Documentation. Accessed: Oct. 8, 2026. [Online]. Available: https://docs.openssl.org/master/man3/SSL_CTX_set_security_level/
- [15] ↩ Microsoft, “curl on Windows,” Microsoft Learn, May 19, 2026. Accessed: Oct. 6, 2026. [Online]. Available: https://learn.microsoft.com/en-us/windows/curl/
- [16] ↩ D. McGrew, “An Interface and Algorithms for Authenticated Encryption,” RFC 5116, Internet Engineering Task Force, Jan. 2008. Accessed: Oct. 5, 2026. [Online]. Available: https://www.rfc-editor.org/rfc/rfc5116
- [17] ↩ OpenSSL Project, “openssl-s_client,” OpenSSL Documentation. Accessed: Oct. 5, 2026. [Online]. Available: https://docs.openssl.org/master/man1/openssl-s_client/
- [18] ↩ a b Wireshark Foundation, “TLS,” Wireshark Wiki. Accessed: Oct. 6, 2026. [Online]. Available: https://wiki.wireshark.org/TLS
Every source cited across the primers is collected on one page.