17 Oct 2026 · security, tls, linux · 6 min
What happens in a TLS handshake
The full TLS 1.3 handshake read 778 bytes from the server. Resuming the session with the ticket the server had handed out, the same connection read 241 bytes. None of those lines land in your server log, and it still decides whether the connection is encrypted or plain noise.
The first message and the fresh key
The ClientHello is 306 bytes and carries two things that matter: the highest version the client accepts, and a freshly generated X25519 public key. Not the server’s key, nothing stored on disk. It is a throwaway key, born for that connection and forgotten after.
Here is what the client sent, when you ask openssl to show the state machine:
openssl s_client -connect example.com:443 -servername example.com -state 2>&1 | head -20
The server answers with the ServerHello, which picks the final version and hands back its own public key. The two halves together produce a secret neither side could compute alone. From there on, the conversation is encrypted with it.
The certificate and what it proves
After the ServerHello comes the certificate. On the one I measured, the server key is P-256 and the signature is ECDSA over SHA-256, in a 607 byte certificate:
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
The certificate is not encrypted, and it should not be. It is the part of the conversation the client has to read in the clear to work out who is on the other side. What it guarantees is the name and the signature, not the content of the page. For what a certificate does and does not settle, the twelve forged certificates case shows the weak side.
On its own it settles nothing without the chain. Each one points at whoever issued it, and that chain has to end at a root the system trusts. Above the root sits the pin that stops an intermediate from issuing any certificate for your name.
Finished and the session ticket
The handshake is only done when both sides send Finished, signed with the secret derived from the keys. That closes off the man in the middle: whoever sits on the path sees the public key but cannot produce a valid Finished.
Then comes the most underrated message of all, which arrives after the handshake is already done: the session ticket, a key that summarizes everything that was negotiated.
Measure the whole handshake on your own machine
The script below starts a local TLS server with a self-signed certificate, connects to it with s_client, and prints the six messages in order. Nothing leaves the machine.
#!/usr/bin/env bash
# handshake: brings up a local TLS 1.3 server, measures full and resumed.
set -u
if ! command -v openssl > /dev/null 2>&1; then
echo "openssl is missing: install it with apt install openssl and run again"
exit 0
fi
DIR="$(mktemp -d)"
PORT=14433
# self-signed certificate, P-256, valid for 2 days
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-nodes -keyout "$DIR/key.pem" -out "$DIR/cert.pem" -days 2 \
-subj "/CN=localhost" -addext "subjectAltName=DNS:localhost" 2>/dev/null
echo "== certificate =="
openssl x509 -in "$DIR/cert.pem" -noout -subject -dates
openssl s_server -accept $PORT -cert "$DIR/cert.pem" -key "$DIR/key.pem" \
-www -tls1_3 > /dev/null 2>&1 &
SRV=$!
sleep 2
ask() { printf 'GET / HTTP/1.0\r\n\r\n'; sleep 2; }
echo
echo "== full handshake, with the states =="
ask | openssl s_client -connect 127.0.0.1:$PORT -servername localhost \
-sess_out "$DIR/sess.pem" -state 2>&1 | grep -E "SSL_connect|Server Temp Key|Cipher is"
echo
echo "== resumed handshake, with the ticket =="
ask | openssl s_client -connect 127.0.0.1:$PORT -servername localhost \
-sess_in "$DIR/sess.pem" 2>&1 | grep -E "Reused|New,|handshake has read"
kill $SRV 2>/dev/null
rm -rf "$DIR"
How to verify
The two lines to look at are the New, and the Reused,. On the first run you get New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384, on the second Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384. The Reused is what tells you the ticket worked.
The self-signed certificate makes verification fail, and that is fine: you will see Verification error: self-signed certificate, which is the correct complaint about a certificate nobody signed. Against a public site the same command shows Verification: OK.
How big the handshake is
Here is the number I like looking at most. A full handshake reads 778 bytes from the server; a resumed one reads 241. That is a 69 percent drop, and it comes down to a single thing: the certificate is 607 bytes and does not need to be sent again.
On a public site with a 4 certificate chain the numbers move: the full handshake read 3,727 bytes on TLS 1.3 and 3,878 on TLS 1.2. The chain weighs more than the version difference, which is why 6 day certificates make sense: less chain to carry.
If it fails
connect: Connection refused when connecting. The s_server never came up. Check whether port 14433 was already taken with ss -ltnp | grep 14433 and switch the script to another one.
openssl is missing. The script says so and stops before running anything else. Install the package and run it again.
Reused never shows up. The server may be missing -tls1_3, or the session expired between the two runs. Run the two lines back to back, without a long pause in between.
What changed from 1.2 to 1.3
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| messages | 2 round trips | 1 round trip |
| certificate signature | over the whole handshake | over the certificate only |
| common cipher | ECDHE-ECDSA-CHACHA20-POLY1305 | TLS_AES_256_GCM_SHA384 |
| bytes read on the public example | 3,878 | 3,727 |
The win in 1.3 is not only time. Taking the signature off the whole handshake makes the server signature cover several sessions, which is the part that makes it cheaper.
FAQ
Is the handshake the same as the connection? No. After Finished the connection is established, and the same key keeps encrypting the data. The handshake happens once, the connection lasts.
Why does the public key travel in the clear in the ClientHello? Because there is no secret yet. Anyone who reads the ClientHello learns the public key of a session they just stopped themselves from decrypting.
Does TLS 1.3 drop the certificate? No. It leaves the critical path and arrives after the key is established, but it is still the only proof of who the server is. For what it proves and does not prove, see the forged certificates and the CAA list.
Is it worth forcing TLS 1.3? It is, and the cost is small: session resumption is itself the mechanism that cuts per-connection work. What is not worth doing is promising 1.3 and leaving old clients with no path.
Sources
- RFC 8446, TLS 1.3, sections 2 and 4, August 2018.
openssl s_client, OpenSSL 3.0.13, measured on 17 October 2026.- openssl(1), how to use s_client, the
-state,-sess_outand-sess_inoptions.