TLS without the wait
HTTPS costs round trips. See real TLS 1.2 and 1.3 handshakes timed, and how resumption, smaller certificates and HSTS make most of the cost disappear.
- 01Count TLS's round tripsSay how many round trips TLS 1.2 and TLS 1.3 add before the request, and what resuming a session and 0-RTT change.
- 02Keep certificates smallExplain why the certificate chain's size matters on a new connection, and why OCSP stapling matters less than it used to.
- 03Skip the http:// detourSay what a redirect from http:// to https:// costs, and how HSTS and the browser's own upgrades skip it.
HTTPS costs round trips
Back to your first visit to completefrontend.com. The browser has found the server's address, as DNS lookups showed. It has opened a line to the server with TCP, as TCP connections and slow start showed. It could ask for the page now. But it doesn't, not yet.
The address starts with https://, and the S stands for secure. On its way, your request passes through computers you don't know: your Wi-Fi router, your internet provider, and others. None of them should be able to read it or change it. So before the browser says a word, it and the server agree on a secret code. That takes a few messages there and back, and each one is a wait.
Here is how long that took when the course connected to completefrontend.com from its own computer:
// Recorded from the course's computer to completefrontend.com.const tcpMs = 23.3; // opening the lineconst tlsMs = 28.5; // agreeing on the secret code, with TLS 1.3 console.log(`Opening the line took ${tcpMs} ms.`);console.log(`Agreeing on the code took ${tlsMs} ms more.`);console.log(`The request could leave after ${(tcpMs + tlsMs).toFixed(1)} ms.`);- Line 1 is a comment. The course connected five times and kept the middle time for each step, so one slow try doesn't change the answer.
- Line 2 is how long TCP took to open the line. Line 3 is how long the secret code took after that.
- Lines 5 and 6 print them:
Opening the line took 23.3 ms.andAgreeing on the code took 28.5 ms more. - Line 7 adds them up. It prints
The request could leave after 51.8 ms.
Here is that first visit, from your browser to the server, with the wait drawn to scale at the bottom:
Your request sits in your browser the whole time. Every message before it passes the strangers on the way.
So agreeing on the code took longer than opening the line. It was more than half of the wait, and the browser hadn't asked for anything yet. Why does it take so long? And how can a site make it shorter? The rest of the lesson answers both questions.
You phone a shop to pay for an order. Before you read out your card number, you and the shop agree on a secret code, so anyone listening on the line hears only nonsense. Agreeing takes a little back-and-forth, and you can't read out the number until it's done.
- In real life: The call connects, and someone says "Hello?"
- In JavaScript: TCP has opened the line
- In real life: Agreeing on a secret code first
- In JavaScript: The TLS handshake
- In real life: Each time you speak and wait for an answer
- In JavaScript: A round trip
- In real life: Reading out your card number, in code
- In JavaScript: The request, sent once TLS is done
That's the job of TLS, short for Transport Layer Security. It's the set of rules a browser and a server follow to agree on a secret code, and then to use it. The back-and-forth at the start is the TLS handshake.
A message and the answer it waits for make one round trip. In Latency vs bandwidth, round trips, not megabits, decided how fast Ravi's page loaded. Count them in the first picture of this lesson, from the top down:
- The first round trip is TCP. The browser asks to open a line, and the server says yes.
- The second round trip is the TLS handshake. Each side sends a key share, its part in making the code. The server also sends its certificate, its proof that it really is completefrontend.com.
- Only in the third round trip does the request go out, and the page starts coming back.
So the handshake costs three things:
- Round trips. Every message that waits for an answer is a round trip. TLS 1.3, the newest version, needs one before the request can leave. The older TLS 1.2 needs two.
- Bytes. The certificate travels in the server's first answer, and it takes up room there.
- Work. Both computers do some maths to make the code and to check the certificate. That takes a few milliseconds.
Before the first request on a new HTTPS connection, the browser and the server agree on a secret code. That costs round trips: two with TLS 1.2, and one with TLS 1.3. On a return visit, TLS 1.3 can even send the request with its very first message. A site makes TLS cheaper in four ways: turn on TLS 1.3, let browsers resume sessions, keep the certificates small, and skip the detour through http://.
Why do one or two round trips matter? Remember Ravi. Every message he sent took 150 ms to reach the server and come back. So each round trip TLS saves lets him ask for the page 150 ms sooner. And it isn't just once per page. Each new host a page uses usually needs its own connection, with its own handshake.
The TLS 1.2 and TLS 1.3 handshakes
Why does TLS 1.2 need two round trips, and TLS 1.3 only one? The answer is in how the two sides agree on a code that nobody listening can learn.
Agreeing on a code in public
The two sides can't just send the code to each other. Anyone listening would see it. So they do this instead:
- Each side makes up a secret number, and never sends it.
- From its secret, each side works out a key share: a number that's safe to send. It sends that.
- Each side mixes its own secret with the other side's key share. The maths is chosen so that both get the same result: the key, the secret code for everything after.
Someone listening sees both key shares, but neither secret, so they can't work out the key. For speed, one thing matters: nothing can be sent in code until both key shares have crossed.
TLS 1.2: ask first, then swap
TLS 1.2 takes it slowly, in two round trips:
- The browser says hello, and lists the kinds of code it can use. The server picks one, and answers with its certificate and its key share.
- The browser sends its key share. Both sides work out the key, and each confirms it got the same one.
Only then can the request leave. On Ravi's line, that's a long wait:
const roundTripMs = 150; // Ravi's round tripconst tcpTrips = 1; // opening the lineconst tlsTrips = 2; // TLS 1.2's handshake const waitMs = (tcpTrips + tlsTrips) * roundTripMs;console.log(`With TLS 1.2, the request waits ${waitMs} ms.`);- Line 1 is Ravi's round trip: 150 ms.
- Lines 2 and 3 count the round trips before the request: one to open the line, and two for TLS 1.2.
- Line 5 adds them up, and multiplies by the round trip.
- Line 6 prints
With TLS 1.2, the request waits 450 ms.
TLS 1.3: guess, and swap at once
TLS 1.3 was published in 2018, in a standard called RFC 8446. It saves a round trip by guessing. In its very first message, the browser says hello and sends its key share, for the kind of code the server will most likely pick. The server answers with its own key share, its certificate, and "ready". Both key shares have crossed in one round trip.
It's like suggesting a time in your first message to a friend, instead of first asking which day suits them. If the guess is wrong, the server asks for another key share, and that costs an extra round trip.
const roundTripMs = 150; // Ravi's round tripconst tcpTrips = 1; // opening the lineconst tlsTrips = 1; // TLS 1.3's handshake const waitMs = (tcpTrips + tlsTrips) * roundTripMs;console.log(`With TLS 1.3, the request waits ${waitMs} ms.`);- Only line 3 changes, and the name on line 6. TLS 1.3 needs one round trip, not two.
- Line 6 prints
With TLS 1.3, the request waits 300 ms.
That's 150 ms saved for Ravi, on every new connection. If your server still speaks only TLS 1.2, turning on TLS 1.3 gives the same saving to every visitor whose browser supports it. The picture puts the two side by side:
What the recording shows
Do real handshakes agree with the counting? The course timed both versions from its own computer to completefrontend.com. It also timed a plain TCP connection there, which takes one round trip:
// Recorded from the course's computer to completefrontend.com.const roundTripMs = 23.3; // one TCP round tripconst tls12Ms = 51.8; // a full TLS 1.2 handshakeconst tls13Ms = 28.5; // a full TLS 1.3 handshake console.log(`TLS 1.2 took ${tls12Ms} ms, ${(tls12Ms / roundTripMs).toFixed(1)} round trips.`);console.log(`TLS 1.3 took ${tls13Ms} ms, ${(tls13Ms / roundTripMs).toFixed(1)} round trips.`);- Line 2 is the round trip: 23.3 ms.
- Lines 3 and 4 are the full handshakes: 51.8 ms for TLS 1.2, and 28.5 ms for TLS 1.3.
- Lines 6 and 7 divide each one by the round trip.
- They print
TLS 1.2 took 51.8 ms, 2.2 round trips.andTLS 1.3 took 28.5 ms, 1.2 round trips.
Two round trips and one, just as counted. In both, about 5 ms is left over after the whole round trips. That's mostly the work: the maths, and checking the certificate. A few milliseconds of work is small next to a round trip, so the round trips are the part to cut.
TLS 1.2 has one trick of its own, an extension called False Start. It lets the browser send its request right after its own "ready", without waiting for the server's. That cuts TLS 1.2 to one round trip too. TLS 1.3 doesn't need the trick.
Now try other lines. Pick a handshake and a round trip. The panel counts the round trips before the request, the same way as the examples:
// For the handshake and the round trip you pick:const tcpTrips = 1;const tlsTrips = handshake.trips; // 2, 1 or 0 const waitMs = (tcpTrips + tlsTrips) * roundTripMs;23 ms47 ms70 ms52 msTLS 1.2, first visit. TCP takes one round trip. TLS adds two more. So the request waits 70 ms at a 23 ms round trip. When the course recorded this handshake to completefrontend.com, the TLS part took 52 ms.
Line 3 of the panel's code takes the TLS round trips of the handshake you picked. Line 5 is the same sum as in the examples. Try the slowest line with each first visit, and see how much the version matters there.
Your own handshake
Your browser did a handshake to load this page, and it wrote down when each step started and ended. Press Look to read those times:
const [page] = performance.getEntriesByType("navigation"); const tcp = page.secureConnectionStart - page.connectStart;const tls = page.connectEnd - page.secureConnectionStart;console.log(page.nextHopProtocol, tcp, tls);Nothing read yet.
Press Look to read this page's own connection. You'll see how long TCP and TLS took when it loaded, and how many of its files needed a TLS handshake of their own.
- Line 1 gets the page's list of times, called its navigation entry.
- Line 3 is TCP. It runs from
connectStart, when the connection began, tosecureConnectionStart, when TLS began. - Line 4 is TLS. It runs from
secureConnectionStarttoconnectEnd, when the connection was ready.
If TLS shows no time, the browser didn't need a new handshake for this page. It used a connection that was already open, and that's the cheapest handshake of all.
Session resumption and 0-RTT
You read this page, close the tab, and come back later. The old connection has been closed, so the browser needs a new one. Does it have to do the whole handshake again, certificate and all?
The first time you arrive at a hotel, the desk checks your ID, takes your details, and gives you a room key. When you come back in the evening, you just show the key and walk on. The checking was done once, and the key proves it.
- In real life: Showing your ID at the desk on the first day
- In JavaScript: A full handshake, with the certificate checked
- In real life: The room key the desk gives you
- In JavaScript: The session ticket
- In real life: Showing just the key when you come back
- In JavaScript: Session resumption
TLS works the same way. After a full handshake, the server gives the browser a session ticket: a small, sealed note that only the server can open. It records what the two of them agreed. Next time, the browser shows the ticket in its first message. This is called session resumption, and it saves three things:
- The server doesn't send its certificate again, so there are fewer bytes.
- Nobody has to check a signature, so there's less work.
- With TLS 1.2, the handshake needs one round trip instead of two.
Browsers use the same trick within one visit. When a page needs several connections to one host, the browser lets the first handshake finish. Then the other connections resume its session.
Here is what resuming saved in the recording, with TLS 1.2:
// TLS 1.2 to completefrontend.com, recorded.const roundTripMs = 23.3;const fullMs = 51.8; // the first handshakeconst resumedMs = 25; // the next one, with the ticket const savedMs = fullMs - resumedMs;console.log(`Resuming saved ${savedMs.toFixed(1)} ms, ${(savedMs / roundTripMs).toFixed(1)} round trips.`);- Line 2 is the same 23.3 ms round trip.
- Line 3 is the first, full handshake: 51.8 ms. Line 4 is the next one, with the ticket: 25 ms.
- Line 6 subtracts the two.
- Line 7 prints
Resuming saved 26.8 ms, 1.2 round trips.
That's one whole round trip, plus a little work. So with TLS 1.2, a returning visitor's browser can send its request one round trip sooner.
What about TLS 1.3? Its full handshake is already one round trip. A resumed one still sends a hello and waits for the answer: one round trip too. So with TLS 1.3, resuming saves bytes and work, but not a round trip. To save that last round trip, TLS 1.3 has one more trick.
At your favourite café, the barista knows you. As you walk in, you call out "The usual!", and your tea is started before you even reach the counter. But if someone recorded your call and played it again, the barista would make a second tea.
- In real life: Being a regular the barista knows
- In JavaScript: Having a session ticket from an earlier visit
- In real life: Calling out "The usual!" as you walk in
- In JavaScript: Sending the request with the first TLS message
- In real life: Your tea started before you reach the counter
- In JavaScript: 0-RTT: no TLS round trip before the request
- In real life: Someone playing a recording of your call
- In JavaScript: A copy of the early request, sent again
That trick is 0-RTT, short for zero round-trip time. On a resumed connection, the browser makes a key from the ticket. Then it sends its request in the same message as its hello, already in code. Data sent this way is called early data. TLS adds no round trip at all before the request:
const roundTripMs = 150; // Ravi's round tripconst tcpTrips = 1; // opening the lineconst tlsTrips = 0; // 0-RTT: the request goes with the hello const waitMs = (tcpTrips + tlsTrips) * roundTripMs;console.log(`With 0-RTT, the request waits ${waitMs} ms.`);- Line 3 now counts no round trips for TLS. The name on line 6 changes too.
- Line 6 prints
With 0-RTT, the request waits 150 ms.Only TCP's round trip is left.
There's a catch, and RFC 8446 spends a whole section on it. Someone on the network can record that early data and send it again. The server can't always tell the copy from the first. For "send me the home page", that's harmless: the same page goes out twice. For "place this order", it could mean two orders. So a server should accept early data only for requests that are safe to repeat.
Resuming and 0-RTT are easy to mix up. Read this table across: each row is one way to set up TLS, how many round trips TLS spends before the request, and what it skips.
| Which handshake | How many TLS round trips before the request? | What does it skip? |
|---|---|---|
| TLS 1.2, first visit | Two. | Nothing. The certificate is sent and checked. |
| TLS 1.3, first visit | One. | Nothing. The certificate is sent and checked. |
| TLS 1.2, resumed | One. | The certificate and the signature checks. |
| TLS 1.3, resumed | One. | The certificate and the signature checks. |
| TLS 1.3, 0-RTT | None. The request goes with the hello. | The certificate, the checks, and the wait. |
Certificate chains and OCSP stapling
In the handshake, the server sends its certificate. What is that, exactly? And why does its size matter for speed?
You visit an office where nobody knows you. You hand the receptionist a letter saying who you are, signed by your manager, and a note from head office saying your manager is real. She knows head office's signature, so she believes the whole pile. But she reads every page before she lets you in.
- In real life: Your letter, signed by your manager
- In JavaScript: The site's certificate, signed by an intermediate
- In real life: Head office's note that your manager is real
- In JavaScript: The intermediate's certificate, signed by a root
- In real life: Head office's signature, which the receptionist already knows
- In JavaScript: The root certificate, already in the browser
- In real life: Every page she has to read
- In JavaScript: Every byte of the chain, sent before the page
A certificate is a small file that says "this key belongs to completefrontend.com". A company that browsers trust signs it. That company is a certificate authority.
Usually the signer isn't trusted directly. It has a certificate of its own, signed by a bigger authority. At the top is a root: a certificate that's built into your browser. The certificates in between are called intermediates. The site's certificate plus its intermediates make the certificate chain, and the server sends all of it.
The chain's share of the first round trip
Why does the size matter? TCP connections and slow start showed that a new connection starts small. In its first round trip, a server can send only about 14,600 bytes. Then it has to wait for the browser to say "got it".
The chain travels in the server's first answer. If that answer is bigger than 14,600 bytes, the server has to stop and wait, and the handshake costs a whole extra round trip. So how much room does the chain take?
const firstRoundTripBytes = 14600; // what a new connection can send at firstconst chainBytes = 6093; // the 3 certificates completefrontend.com sends const share = Math.round((chainBytes / firstRoundTripBytes) * 100);console.log(`The certificates fill ${share}% of the first round trip.`);- Line 1 is the room in the first round trip: 14,600 bytes.
- Line 2 is this site's recorded chain: 3 certificates, 6,093 bytes.
- Line 4 works out the chain's share, as a percentage.
- Line 5 prints
The certificates fill 42% of the first round trip.
It fits, with room to spare. That's about 2 KB for each certificate. A longer chain, or bigger certificates, could fill the room and cost that extra round trip.
The size of a certificate depends on its kind of key. Many certificates use RSA keys, and this site's do too. Newer ones can use ECDSA keys, which are much smaller for the same safety. cloudflare.com uses ECDSA, and the course recorded its chain too:
const firstRoundTripBytes = 14600; // what a new connection can send at firstconst chainBytes = 2558; // the 3 certificates cloudflare.com sends const share = Math.round((chainBytes / firstRoundTripBytes) * 100);console.log(`The certificates fill ${share}% of the first round trip.`);- Only line 2 changes: 3 certificates again, but 2,558 bytes.
- Line 5 prints
The certificates fill 18% of the first round trip.
That's the same number of certificates in less than half the bytes. Here are both chains, drawn to scale:
OCSP stapling
One more thing about certificates. Sometimes a certificate has to be cancelled before it runs out, for example when its secret key is stolen. That's called revoking it. How does a browser find out?
The old way was OCSP, the Online Certificate Status Protocol. The browser asked the authority's OCSP server: "Is this certificate still good?" That meant another connection and another wait, before the page.
A server could save its visitors that trip with OCSP stapling. It fetched a fresh, signed "still good" answer itself, and attached it to its handshake. It's like attaching a recent bank letter to a form, so the office doesn't have to phone your bank.
Today, stapling matters much less for speed. The Chromium project says Chrome doesn't generally check OCSP online. It uses its own lists of revoked certificates instead, called CRLSets. And Let's Encrypt, a certificate authority, turned off its OCSP servers on August 6, 2025. Neither of the recorded sites stapled an OCSP answer.
Read this table across: each row is one thing a site can do about its certificates, and why it helps.
| What to do | Why it helps |
|---|---|
| Send the intermediates, but not the root. | Browsers already have the root, so sending it only adds bytes. |
| Use ECDSA keys. | Their certificates are smaller than RSA ones, so the chain takes less room. |
| Keep the chain short. | Every certificate adds bytes: about 2 KB each in this site's chain. |
| Don't count on OCSP stapling for speed. | Chrome doesn't generally check OCSP online, and Let's Encrypt has turned its OCSP servers off. |
HSTS and the redirect it saves
Someone types completefrontend.com with http:// in front, or clicks an old link that starts with http://. What happens?
Plain http:// has no secret code, and this site doesn't send its pages that way. So the server answers with a redirect: a short reply with the status 301, which means "this has moved for good", and the new address, https://completefrontend.com/. Only then does the browser open the real, private connection. The course timed that detour:
// http://completefrontend.com, recorded.const roundTripMs = 23.3;const detourMs = 45.7; // open a line, ask, and get "301: use https://" console.log(`The detour took ${detourMs} ms, ${(detourMs / roundTripMs).toFixed(1)} round trips.`);- Line 2 is the same 23.3 ms round trip.
- Line 3 is the recorded detour: opening a line to http://, asking, and getting the
301back. It took 45.7 ms. - Line 5 prints
The detour took 45.7 ms, 2.0 round trips.
That's two whole round trips, and neither brought a single byte of the page. On Ravi's line, it shows even more:
const roundTripMs = 150; // Ravi's round tripconst detour = 2; // open a line to http://, ask, and get the redirectconst https = 1 + 1 + 1; // TCP, TLS 1.3, then ask for the page console.log(`From http://, the page starts arriving after ${(detour + https) * roundTripMs} ms.`);console.log(`From https://, it starts arriving after ${https * roundTripMs} ms.`);- Line 2 is the detour: two round trips.
- Line 3 is the real visit: one round trip for TCP, one for TLS 1.3, and one to ask for the page.
- Line 5 prints
From http://, the page starts arriving after 750 ms. - Line 6 prints
From https://, it starts arriving after 450 ms.
The detour costs Ravi 300 ms before anything useful happens. The picture shows the two visits as round trips:
The first time you visit a friend's building, the front door is locked, with a sign: "Use the side door." You walk round. Next time, you remember and go straight to the side door, and you save the walk.
- In real life: The locked front door, with a sign
- In JavaScript: http://, answered with a redirect
- In real life: Walking round to the side door
- In JavaScript: Opening the real https:// connection
- In real life: Going straight to the side door next time
- In JavaScript: HSTS: the browser switches to https:// by itself
- In real life: How long you trust your memory
- In JavaScript:
max-age, in seconds
A site can ask the browser to remember. With its https:// pages, it sends a header called Strict-Transport-Security, or HSTS for short. It says: for the next max-age seconds, only ever use https:// for this site. This site sends max-age=31556926, which is about a year.
After one visit, the browser changes http:// to https:// by itself, before anything leaves the computer. There's no detour.
What about the very first visit, before the browser has seen the header? Chrome and Safari now switch every http:// page to https:// on their own, whatever the site says. hstspreload.org explains this. It also runs the HSTS preload list, a list of HTTPS-only sites built into browsers. Because browsers now switch on their own, it says the list adds little, and it no longer recommends joining it.
HSTS itself is still recommended. If an attacker blocks https:// to push the browser back to http://, HSTS makes sure it never falls back. And for browsers that don't switch on their own, it still saves the detour.
Which fix?
Each problem below makes a visit wait, in the handshake or on the way to it. Each has its own fix. Sort them:
- Every new connection spends two round trips on TLS
- The server only speaks TLS 1.2
- A returning visitor's browser checks the whole certificate again
- Each of a browser's connections to the same host does a full handshake
- The server sends four certificates, 9 KB, before any of the page
- An RSA chain fills 40% of the first round trip
- Old links to http:// go through a redirect on every visit
- On café Wi-Fi, an attacker could rewrite the http:// redirect and keep a returning visitor on http://
Sort each problem by the change that fixes it.
Common mix-ups
- "HTTPS is slow." No. With TLS 1.3, a new connection spends one round trip on TLS. A connection that's already open spends none.
- "Resuming a session always saves a round trip." No. It does only with TLS 1.2. With TLS 1.3, resuming saves bytes and work, and 0-RTT is what saves the round trip.
- "0-RTT is free speed." No. Someone can record early data and send it again. It's only safe for requests that can be repeated.
- "The certificate is just a formality." No. It's real bytes in the server's first answer: 6,093 of them for this site's chain.
- "Every browser checks OCSP on every visit." No. Chrome doesn't generally check online, and Let's Encrypt has turned its OCSP servers off.
- "HSTS makes the TLS handshake faster." No. The handshake is the same. HSTS skips the http:// detour that comes before it.
Practice exercises
Answer first, then use the hints.
A satellite internet connection has a 600 ms round trip. Here is the TLS 1.2 example on that line. What does it print?
const roundTripMs = 600; // a satellite link
const tcpTrips = 1; // opening the line
const tlsTrips = 2; // TLS 1.2's handshake
const waitMs = (tcpTrips + tlsTrips) * roundTripMs;
console.log(`With TLS 1.2, the request waits ${waitMs} ms.`);With TLS 1.2, the request waits 1800 ms. That's three round trips of 600 ms each. TLS 1.3 would save 600 ms of it on every new connection, and slow lines like this one gain the most.
Another site sends a longer RSA chain of 9,100 bytes, so line 2 now holds that. What does the example print?
const firstRoundTripBytes = 14600; // what a new connection can send at first
const chainBytes = 9100; // a longer RSA chain
const share = Math.round((chainBytes / firstRoundTripBytes) * 100);
console.log(`The certificates fill ${share}% of the first round trip.`);The certificates fill 62% of the first round trip. It still fits, but less than 6 KB of room is left. A chain over 14,600 bytes would cost a whole extra round trip.
This server only speaks TLS 1.2, so line 3 now counts 1 + 2 + 1. What does the first line print?
const roundTripMs = 150; // Ravi's round trip
const detour = 2; // open a line to http://, ask, and get the redirect
const https = 1 + 2 + 1; // TCP, TLS 1.2, then ask for the page
console.log(`From http://, the page starts arriving after ${(detour + https) * roundTripMs} ms.`);
console.log(`From https://, it starts arriving after ${https * roundTripMs} ms.`);From http://, the page starts arriving after 900 ms. That's six round trips of 150 ms. An old TLS version and a detour together make the wait twice as long as a plain TLS 1.3 visit, which takes 450 ms.
These are the recorded TLS 1.3 handshakes, full and resumed, in the same subtraction as the TLS 1.2 example. What does it print? Did resuming save a round trip?
// TLS 1.3 to completefrontend.com, recorded.
const roundTripMs = 23.3;
const fullMs = 28.5; // the first handshake
const resumedMs = 23.8; // the next one, with the ticket
const savedMs = fullMs - resumedMs;
console.log(`Resuming saved ${savedMs.toFixed(1)} ms, ${(savedMs / roundTripMs).toFixed(1)} round trips.`);Resuming saved 4.7 ms, 0.2 round trips. That's the work and the certificate's bytes, not a round trip. A resumed TLS 1.3 handshake still waits for one answer. Only 0-RTT saves that round trip.
In DevTools' Network panel, open the Timing tab of a request that opened a new HTTPS connection. What is the row for the TLS handshake called?
SSL. DevTools still calls the TLS handshake by its old name. Its time is part of Initial connection, and it also gets a row of its own.
A site serves its HTML from www.example.com and its pictures from img.example.com. The browser opens one connection to each host. On a first visit, how many full TLS handshakes happen?
Two, one for each host. In some cases HTTP/2 can share one connection between two hosts. HTTP/1.1 and HTTP/2, the next lesson, explains when.
Check your understanding
For each question, ask: is this about round trips, bytes, or work?
Question 1 of 7What does the TLS 1.3 example print?
Read the code, then predictconst roundTripMs = 150; // Ravi's round trip const tcpTrips = 1; // opening the line const tlsTrips = 1; // TLS 1.3's handshake const waitMs = (tcpTrips + tlsTrips) * roundTripMs; console.log(`With TLS 1.3, the request waits ${waitMs} ms.`);Choose an answer to see the explanation.
Question 2 of 7Why does a full TLS 1.3 handshake take one round trip less than TLS 1.2's?
Choose an answer to see the explanation.
Question 3 of 7On a return visit, what does session resumption let the browser and the server skip?
Choose an answer to see the explanation.
Question 4 of 7What's the risk of TLS 1.3's 0-RTT early data?
Choose an answer to see the explanation.
Question 5 of 7What does the chain example print?
Read the code, then predictconst firstRoundTripBytes = 14600; // what a new connection can send at first const chainBytes = 6093; // the 3 certificates completefrontend.com sends const share = Math.round((chainBytes / firstRoundTripBytes) * 100); console.log(`The certificates fill ${share}% of the first round trip.`);Choose an answer to see the explanation.
Question 6 of 7Why does OCSP stapling matter less for speed than it used to?
Choose an answer to see the explanation.
Question 7 of 7What does HSTS do for speed?
Choose an answer to see the explanation.
Key takeaways
- Before the first request on a new HTTPS connection, the browser and the server agree on a secret code: the TLS handshake.
- TLS 1.2 spends two round trips on it, and TLS 1.3 spends one. Turn TLS 1.3 on.
- Resuming a session skips the certificate and the checks. With TLS 1.2, it also saves a round trip.
- 0-RTT sends the request with the very first message. Use it only for requests that are safe to repeat.
- The certificate chain travels in the server's first answer. Keep it short, leave out the root, and prefer ECDSA.
- OCSP stapling matters less now, because Chrome doesn't generally check OCSP online.
- A detour through http:// costs about two round trips. HSTS, and browsers that switch to https:// on their own, skip it.
Remember the one-liner.
One round trip to agree on the code, none on a return visit with 0-RTT, and never a detour through http://.
Sources
The facts on this page come from these standards, books and docs:
- Transport Layer Security (TLS), High Performance Browser Networking, Ilya Grigorik.
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3, IETF.
- CRLSets, The Chromium Projects.
- Ending OCSP Support in 2025, Let's Encrypt.
- HSTS Preload List Submission, hstspreload.org.
- Strict-Transport-Security, MDN.
- PerformanceResourceTiming: secureConnectionStart, MDN.
- RequestTimingView.ts: the Network panel's Timing tab, Chrome DevTools source.
Coming next: HTTP/1.1 and HTTP/2. The line is open and private. Now, how many requests can it carry at once? The next lesson goes from six connections per host to one shared connection, and which old tricks to stop using.