Host Talk / The TLS handshake in slow motion

The TLS handshake in slow motion

HOST TALK

10 min read · 2,199 words

Before a browser sends your password to a website, two things have to happen. The two sides must agree on how to scramble the conversation, and the browser must be convinced it is talking to the right server rather than someone sitting in the middle. That negotiation is the TLS handshake. With current versions it takes a single round trip, which is a good deal faster than it used to be.

Most people only meet the handshake when it breaks: a padlock turns into a warning page, a script fails with "certificate verify failed", an old phone refuses to load anything. Those failures are much easier to read once you know what each message in the exchange is for, and what the browser is checking at each point.

This piece walks through the handshake in slow motion, using TLS 1.3 as the main example because that is what browsers and well-kept servers use today. It also shows what changed from the older version, where the time goes, and how to watch a handshake happen from your own terminal.

Why a handshake exists at all

Encryption is easy once both sides share a secret key. The hard part is getting that key to both sides when the only channel between them is the open internet, where anyone along the route can read every packet. You cannot just send the key, because the person reading the packets would get it too.

The second problem is identity. Even if the conversation is private, a private conversation with an impostor is worth nothing. So the handshake has to do two jobs at once: agree on a secret nobody else can work out, and prove that the server on the other end really is the site named in the address bar.

It also has to be negotiable. Servers and browsers are updated at different times by different people, so the first thing each side does is say what it can speak. They then settle on the best option they have in common. That is why a modern browser can still talk to a server that was set up some years ago, and why a very old server eventually stops working with anyone.

The sequence, message by message

The browser opens with a message called ClientHello. It contains the protocol versions it supports, a list of cipher suites (combinations of encryption and hashing methods), a block of random bytes, and the name of the site it wants, known as the server name indication. That name matters because one server on one IP address may hold dozens of certificates. Without it the server would not know which one to present. The ClientHello also carries the browser's half of a key exchange, a public value generated just for this connection.

The server answers with ServerHello. It picks one protocol version and one cipher suite from the browser's list, and adds its own public value for the key exchange. At this point both sides hold enough information to calculate the same secret, and they do. From here on, everything in TLS 1.3 is encrypted, including the certificate.

Next, still in the same flight of messages, the server sends its certificate chain and a signature, which in the protocol is called CertificateVerify. The signature is made with the private key that matches the certificate, over a summary of everything said so far. That is the proof of identity: only the holder of the private key could have produced it. Both sides finish with a short Finished message, a check that neither had the conversation tampered with, and then application data starts to flow.

Browser Server ClientHello: versions, ciphers, site name, key share ServerHello: chosen cipher, key share Certificate, signature, Finished (encrypted) Finished, then the HTTP request (encrypted) One round trip before data flows
The TLS 1.3 handshake: the browser sends one message, the server answers with everything else, and the first request can follow straight away.

How both sides get the same key without sending it

This is the clever part, and it is worth a moment. Each side generates a private number and derives a public value from it. They swap the public values in the hello messages. Using its own private number and the other side's public value, each side can calculate a shared secret. The mathematics (in practice, elliptic curve Diffie-Hellman, usually on a curve called X25519) is built so that the two calculations give the same answer, while someone who only saw the two public values cannot work it out.

The usual analogy is mixing paint. You and a friend each pick a secret colour, and you agree publicly on a shared base colour. Each of you mixes your secret into the base and swaps the results. Each of you then adds your own secret to the mix you received. You both end up with the same final colour, and an observer who saw only the mixtures cannot unmix them. The analogy is rough, but it gets the shape right: the secret never travels.

The result is then stretched into several keys, one for each direction of traffic and one for the handshake itself. Those are the keys that actually encrypt your page requests. They exist only for this connection.

What the browser checks

When the certificate arrives, the browser runs through a short list, and failing any item produces the warning page.

  1. The certificate names the host it asked for. The name must appear in the certificate's list of subject alternative names, either exactly or through a wildcard that covers that level.
  2. The dates are valid. Today must fall between the start and end of the certificate's life. An expired certificate and a certificate checked on a device with the wrong clock look exactly the same.
  3. The chain of signatures leads to an authority the browser already trusts. The server's certificate was signed by an intermediate, which was signed by a root in the browser's store.
  4. The signature in the exchange matches the certificate's public key, which proves the server holds the private key.
  5. The certificate has not been revoked, where the browser checks that at all. Revocation is patchy in practice, and short certificate lifetimes do most of the work there.

None of these checks says anything about whether the site is honest. A certificate proves you are connected to whoever controls that domain name, and that nobody can read or change the traffic on the way. A scam site can have a perfectly valid padlock.

TLS 1.2 versus TLS 1.3

The older TLS 1.2 needed two round trips for a full handshake, because the key exchange happened in a second step after the hellos. TLS 1.3, finished in 2018, folded the key share into the first message, so the server can compute keys immediately. It also removed a long list of weak options that earlier versions allowed.

PointTLS 1.2TLS 1.3
Round trips for a full handshakeTwoOne
Certificate visible to an observerYesNo, it is encrypted
Forward secrecyOnly if the server picks the right suiteAlways
Weak legacy options (RC4, static RSA key exchange, CBC modes)Possible if configuredRemoved from the protocol
Browser supportUniversalAll current browsers

Browsers dropped TLS 1.0 and 1.1 around 2020, so a server limited to those versions is unreachable for ordinary visitors. Keeping TLS 1.2 enabled for older clients and scripts is fine. What matters is that 1.3 is available too.

Illustrative: 50 ms round trip, new connection, time until the request can be sent TLS 1.2 TCP 50 TLS 100 ms 150 ms TLS 1.3 TCP 50 TLS 50 100 ms HTTP/3 QUIC + TLS 50 50 ms
Each round trip is paid on a new connection, which is why fewer handshakes and reused connections matter more than clever tuning.

What the handshake costs, and how connections avoid paying twice

A full handshake adds at least one round trip on top of the TCP setup. On a fast fixed line to a nearby server that is a few milliseconds. From a phone on a poor signal, to a server on another continent, a round trip can be a quarter of a second, and everything on the first visit waits for it.

There are three common ways to avoid paying repeatedly. The first is keeping connections open: HTTP/2 sends many requests down one connection, so a page with sixty resources from the same host does one handshake, not sixty. The second is resumption. After a first visit the server hands the browser a ticket, and on a return visit the browser can use it to skip the certificate exchange and set up keys faster. The third is HTTP/3, which runs over QUIC and combines the transport setup with TLS 1.3, bringing a new connection down to a single round trip in total.

TLS 1.3 also allows "0-RTT" data on resumed connections, where the browser sends its first request alongside the ticket. It is faster, but that early data can be replayed by an attacker, so servers only accept it for requests that are safe to repeat, like fetching a page. You generally do not have to think about this, and it is one reason to leave the defaults on a managed host alone.

Forward secrecy

Because each session key is made fresh and thrown away, recording today's traffic and stealing the server's private key next year does not let anyone read it. The private key signs the handshake to prove identity, but it never encrypts the traffic and never takes part in producing the session key. That property is called forward secrecy, and TLS 1.3 gives it to every connection by default.

It answers a worry that was once very real: an agency or thief collecting encrypted traffic in bulk, then waiting until the key leaks. With older setups that used the server's key directly to protect the session secret, a later leak exposed the whole archive. With ephemeral keys, there is nothing to find. Each conversation is sealed with a key that no longer exists anywhere.

When the handshake goes wrong

Most failures map neatly onto one of the checks or onto the negotiation step. These are the ones that turn up in support tickets most often.

SymptomUsual causeWhat to do
NET::ERR_CERT_DATE_INVALIDCertificate expired, or the visitor's clock is wrongCheck the expiry date, then the device clock
NET::ERR_CERT_COMMON_NAME_INVALIDThe name typed is not on the certificateReissue with the right names, or fix the redirect
NET::ERR_CERT_AUTHORITY_INVALIDMissing intermediate, self-signed certificate, or a network device intercepting trafficInstall the full chain from your authority
ERR_SSL_VERSION_OR_CIPHER_MISMATCHNo protocol or cipher in commonEnable TLS 1.2 and 1.3 and a modern cipher list
Works in a browser, fails in a scriptScript does not fetch missing intermediates or uses an old trust storeServe the full chain; update the script's CA bundle
Works on a laptop, fails on one phoneDevice has no root that your chain needs, or the wrong dateCheck the device's OS version and clock

The missing intermediate deserves a special mention because it is the most confusing. Some clients cache intermediates they saw on other sites and fill the gap, so the site looks fine to you and broken to a customer. Your own browser is the worst possible test. Use a tool that does not remember anything, as shown below.

Commands worth running

You can watch a handshake from any terminal that has OpenSSL or curl. The first command connects, sends the site name, and prints the certificate chain and negotiated settings:

openssl s_client -connect example.com:443 -servername example.com -tls1_3 < /dev/null

Near the end of the output, look for lines like these:

Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384
Verification: OK

If the server cannot do TLS 1.3, the command fails with a handshake error, and removing the -tls1_3 flag shows what it does offer. To see the chain the server actually sends, add -showcerts. If you count only one certificate and the issuer is an intermediate, the chain is incomplete.

For timing, curl will split a request into stages:

curl -so /dev/null -w 'connect %{time_connect}  tls %{time_appconnect}  first byte %{time_starttransfer}\n' https://example.com/

The gap between the connect figure and the tls figure is roughly the handshake. Run it a few times; the first run can be slower. If you manage the certificate yourself, the SSL expiry checker will tell you how long you have left, and the troubleshooting guide covers what to do when a warning page appears.

Questions that come up

Does the padlock mean the site is safe?

It means the connection is private and the server controls the domain shown. It says nothing about the intentions of the people running it.

Can a hosting provider see my encrypted traffic?

The visitor's connection ends at your web server, so the host's own server can see the decrypted requests by design, since it has to serve them. The encryption protects the path across the internet, not the machine at the end.

Why does the site name travel in the clear?

The server name indication is sent before encryption starts, so networks along the way can see which hostname you are visiting, though not the page or its content. An extension called Encrypted Client Hello hides this where both sides support it, and deployment is still uneven.

Do I need to configure ciphers myself?

On shared and managed hosting, no. The provider's defaults are usually better than a hand-edited list. On your own VPS, start from a published modern profile for your web server and change as little as you can.

PreviousWhen a CDN helps, and when it gets in the wayNextCertificate authorities and how browsers decide who to trust

More from Host Talk

Host Talk

What actually happens when you type a URL

You type a web address, press Enter, and a page shows up. It feels instant, which is a little unfair to...

Host Talk

Reading an access log

Your web server writes down every request it handles. This file, the access log, is the most reliable account...

Host Talk

Cookies, sessions and why logins fight with caches

The web is stateless by design. Each request arrives with no memory of the previous one. Cookies are how...