Wat er gebeurt vóór de eerste byte
De browser weet nu dat wallieweb.nl op 52.236.137.214 woont. Toch gaat er nog geen byte HTML over de lijn. Eerst moeten laptop en server een verbinding opzetten en afspreken hoe ze die versleutelen. Elke ronde van dat heen-en-weer kost een round trip (RTT): de tijd die een pakketje nodig heeft om naar de server en terug te gaan.
Van mijn verbinding thuis naar de server van wallieweb.nl is dat ongeveer 21 milliseconden. Hieronder zie je waar die rondes heen gaan en hoe TLS 1.3 en QUIC ze wegsnoeien.
52.236.137.214:443 · nog niets verstuurd
RTT naar de server: ≈ 21 ms (gemeten)
Een adres, nog geen verbinding
De browser heeft een IP-adres en een poort, 443 voor HTTPS. Voordat er een GET / kan vertrekken, zijn er twee dingen nodig: een betrouwbare verbinding en een gedeelde sleutel. Tijd tellen we hier in RTT's, want het rekenwerk is snel; het wachten op de lijn niet.
TCP: SYN, SYN-ACK, ACK
TCP begint met de three-way handshake. De laptop stuurt een SYN met een willekeurig startvolgnummer. De server antwoordt met SYN-ACK: zijn eigen volgnummer plus een bevestiging van dat van de laptop. De laptop bevestigt met ACK.
Na één RTT is de verbinding open, en met die laatste ACK mag al data meegaan. curl meet dit als time_connect: bij wallieweb.nl zo'n 22 ms. Versleuteld is er nog niets.
TLS 1.2: twee rondes extra
In TLS 1.2 stuurt de laptop een ClientHello met de cipher suites die hij kent. De server kiest er een en stuurt ServerHello, zijn certificaat, zijn deel van de Diffie-Hellman-uitwisseling (ServerKeyExchange) en ServerHelloDone. Pas dan stuurt de laptop zijn eigen sleuteldeel, ChangeCipherSpec en Finished, en wacht op de Finished van de server.
Twee RTT's voor TLS, bovenop die ene voor TCP: het verzoek vertrekt na drie RTT's, de eerste byte van het antwoord komt na vier. Dwing je curl naar TLS 1.2, dan duurt het TLS-deel bij wallieweb.nl 56 ms in plaats van 28.
TLS 1.3: gokken op de sleutel
TLS 1.3 (RFC 8446, 2018) haalt een hele ronde weg. De laptop wacht niet tot de server een algoritme kiest en stuurt in de ClientHello meteen een key_share mee voor de groep die hij verwacht, bijvoorbeeld X25519. Daarmee leidt de server direct de sleutels af en stuurt hij alles in één vlucht terug: ServerHello, het certificaat, de handtekening daarover (CertificateVerify) en Finished.
De laptop stuurt zijn Finished en het GET-verzoek samen. Verzoek na twee RTT's, eerste byte na drie. Gokt de laptop verkeerd, dan vraagt de server met een HelloRetryRequest om een andere groep en kost het alsnog een ronde extra.
Minder rondes, en minder te zien
Vanaf de ServerHello is in TLS 1.3 alles versleuteld, ook het certificaat. Wie op het netwerk meekijkt, ziet bij TLS 1.2 nog welk certificaat de server stuurt; bij TLS 1.3 niet meer.
De ClientHello zelf gaat wel onversleuteld over de lijn, met in het SNI-veld de naam van de site. Encrypted Client Hello (ECH) moet dat oplossen, maar wordt nog lang niet overal ondersteund. TLS 1.3 schrapte verder de statische RSA-sleuteluitwisseling: elke uitwisseling met publieke sleutels is nu forward secret.
QUIC: transport en crypto in één ronde
QUIC (RFC 9000) draait over UDP en brengt zijn eigen betrouwbaarheid, volgorde en congestiecontrole mee. Een aparte transporthandshake is er niet. De TLS 1.3-berichten reizen in QUIC's eigen CRYPTO-frames, dus verbinding en sleutels worden in één ronde geregeld.
Het eerste Initial-pakket van de client wordt opgevuld tot minstens 1.200 bytes. Zolang de server het adres van de client niet heeft gevalideerd, mag hij niet meer dan drie keer zoveel terugsturen. Daarmee is QUIC weinig waard voor amplificatie-aanvallen. HTTP/3 (RFC 9114) is HTTP bovenop QUIC. Verzoek na één RTT, eerste byte na twee.
0-RTT: het verzoek gaat meteen mee
Ben je eerder bij deze server geweest, dan heeft die je een session ticket gegeven. Met de pre-shared key (PSK) uit dat ticket kan de laptop bij het volgende bezoek al sleutels afleiden voordat de server iets heeft gezegd. Het GET-verzoek gaat dan als early data mee in de allereerste vlucht.
Vóór het verzoek zit geen handshake-ronde meer, dus de eerste byte komt na één RTT. Dat kan ook met TLS 1.3 over TCP, maar daar gaat de TCP-handshake nog voor.
De prijs: replay
Early data wordt verstuurd voordat de server zijn eigen willekeurige bijdrage heeft geleverd. Een aanvaller kan het pakket dus kopiëren en later nog eens afspelen, en de server ziet het verschil niet. Voor een GET van een blogpagina maakt dat weinig uit, voor "maak €100 over" wel.
RFC 8446 is daar streng over: een client mag in early data niets sturen wat niet veilig herhaald kan worden. RFC 8470 regelt het voor HTTP. Met de header Early-Data: 1 laat een client of proxy weten dat een verzoek als early data binnenkwam, en de server kan met 425 Too Early vragen om het na de volledige handshake opnieuw te sturen.
Speel zelf
Kies een protocol en schuif met de RTT. Bij de 21 ms die ik thuis meet, scheelt TLS 1.2 tegenover QUIC 0-RTT 63 ms. Dat merk je nauwelijks. Zet de slider op 150 ms, ongeveer de RTT van Nederland naar de Amerikaanse westkust, en het verschil wordt 450 ms, nog voordat de server iets heeft gedaan.
Wat wallieweb.nl zelf doet
Gemeten op 24 september 2026 vanaf een Nederlandse verbinding, mediaan van 11 runs met curl: TCP-verbinding na 22 ms, TLS klaar na 50 ms, eerste byte na 94 ms. De server spreekt TLS 1.3 met TLS_AES_256_GCM_SHA384 en een ECDSA-certificaat (P-256). Met een recente OpenSSL als client wordt de hybride post-quantum groep X25519MLKEM768 afgesproken.
Dan de minder mooie kant. Via ALPN biedt de server alleen http/1.1 aan, dus geen HTTP/2. Er komt ook geen alt-svc-header mee, dus geen HTTP/3 en geen QUIC. Session resumption werkt wel (het ticket is 300 seconden geldig), maar de server staat geen early data toe: Max Early Data: 0. Hier blijft het dus bij twee RTT's vóór het verzoek.
Zelf proberen
Tijden per fase:
curl -so /dev/null -w 'connect %{time_connect} tls %{time_appconnect} eerste byte %{time_starttransfer}\n' https://wallieweb.nl/
Met --tls-max 1.2 erbij zie je het verschil met TLS 1.2. Welke versie, cipher en groep de server spreekt, en welke protocollen hij via ALPN aanbiedt:
openssl s_client -connect wallieweb.nl:443 -servername wallieweb.nl -tls1_3 -alpn h2,http/1.1 </dev/null
Of een site HTTP/3 aanbiedt, zie je aan de alt-svc-header:
curl -sI https://www.cloudflare.com/ | grep -i alt-svc
curl --http3 werkt alleen als je curl met HTTP/3-ondersteuning is gebouwd. De versie die macOS meelevert, kan het niet.
Tot slot
Ik ging ervan uit dat mijn eigen site allang HTTP/2 sprak. Niet dus: keurig TLS 1.3 met een post-quantum sleuteluitwisseling, en daarachter gewoon HTTP/1.1. Bij 21 ms RTT merk je daar weinig van, maar ik had het zonder deze meting nooit gezien. HTTP/2 aanzetten staat nu op mijn lijstje; over QUIC denk ik nog even na.