Encrypted Client Hello: The TLS Extension That Finally Hides Which Site You're Visiting
Here's a fact that surprises people who think TLS 1.3 solved privacy on the wire: anyone watching your traffic can still see exactly which hostname you're connecting to, even over a fully encrypted HTTPS connection. Not the page, not the query string, but the domain name, sitting there in plaintext in the very first packet of the handshake. You can check this yourself without decrypting anything:
openssl s_client -connect example.com:443 -tls1_3 -servername example.com 2>/dev/null \
| head -1
tshark -r capture.pcap -Y "tls.handshake.type == 1" \
-Tfields -e tls.handshake.extensions_server_name
The second command reads the Server Name Indication (SNI) extension straight out of a packet capture, no keys involved. SNI exists because a single IP address can host thousands of TLS sites (think Cloudflare, or any shared hosting box), and the server needs to know which certificate to present before the handshake has agreed on any encryption. So the client tells it, in the clear, in the ClientHello. Every network operator between you and the server, every ISP, every state-level censor, every workplace proxy, gets a free list of every site you visit, timestamped, even though the actual page content is invisible to them.
Why the first attempt at fixing this went nowhere
This isn't a new problem. Encrypted SNI (ESNI) was drafted years ago as a fix, and it had an obvious flaw: it encrypted the SNI field but left the rest of the ClientHello's shape untouched, and it published its encryption key in a DNS TXT record that itself became a fingerprint. Researchers found ways to correlate ESNI-using connections back to individual sites within weeks of it shipping in Firefox. It also didn't handle the case where the server didn't recognise the key the client used, which happened constantly during key rotation, and the fallback behaviour leaked information too. ESNI was quietly withdrawn.
Encrypted Client Hello (ECH) is the redesign, and the difference in approach is worth understanding because it explains both why ECH actually works and why it still needs a supporting cast to be effective.
Splitting the ClientHello in two
Instead of encrypting one field, ECH encrypts the entire ClientHello. The client builds two of them:
- An inner ClientHello containing the real SNI, the real ALPN list, everything the connection actually needs.
- An outer ClientHello, sent on the wire, whose SNI is a shared, public "cover name" that tells an observer nothing beyond "this is a TLS connection to some server behind this front".
The inner ClientHello is encrypted using HPKE (Hybrid Public Key Encryption, RFC 9180), the same primitive combination used elsewhere in modern crypto: an ephemeral key exchange, a KDF, an AEAD. The public key it encrypts to isn't fetched at connection time; it's published in advance, in DNS, as part of an HTTPS (SVCB-family) resource record:
example.com. IN HTTPS 1 . alpn="h2" ech="AEX+DQBBmwAgACAvbdgw2u+..."
The client resolves that record, extracts the ECH configuration (key ID, HPKE cipher suite, public key), and uses it to seal the inner ClientHello into an extension carried inside the outer one. The server, if it's the "client-facing server" for that cover name, unseals the inner ClientHello with its private key, discovers the real SNI, and either serves the connection itself or hands it off internally to the right backend.
Handling key rotation without leaking anything
If the server can't decrypt the inner ClientHello, because it's using an ECH config the client hasn't seen yet, it responds with a retry_configs extension carrying the current configuration, and the client retries the handshake with fresh keys. Crucially, this rejection path never reveals the real SNI to a network observer; all they see is a handshake, a rejection, and a retry with a slightly different outer message. It's a lot less exciting to fingerprint than ESNI's failure mode ever was.
GREASE: hiding the fact that you're hiding anything
One neat detail: browsers with ECH support send a GREASE ECH extension, filled with random-looking bytes, even when connecting to a server that publishes no ECH config at all. The point isn't cryptographic; it's about the shape of the traffic. If "ClientHello has an encrypted_client_hello extension" only ever appeared on real ECH connections, a censor could use its mere presence as a signal that a site cares about privacy, or single out ECH-shaped handshakes for extra scrutiny. Sending the same-shaped noise everywhere denies that signal for free.
Where the privacy actually comes from
ECH's guarantee is only as good as the cover name it hides behind. If a client-facing server serves exactly one site, hiding its SNI achieves nothing: the observer just notes the IP address and looks up which domain resolves there. ECH's practical benefit exists because large providers front many unrelated domains behind the same edge IPs and the same cover name, so an outside observer sees "a connection to Cloudflare's shared ECH front" rather than "a connection to this specific customer's site". The bigger and more mixed that anonymity set, the more the extension is worth. A self-hosted server on its own dedicated IP, sitting behind no CDN, gets essentially nothing from turning ECH on: the IP address alone already identifies it.
That's also why ECH depends on encrypted DNS to mean anything. Fetching the HTTPS record over plain UDP port 53 puts the query name right back in plaintext for anyone watching, cancelling out the point of encrypting the ClientHello. ECH only delivers on its promise paired with DNS-over-TLS, DNS-over-HTTPS, or an equivalent, so the actual privacy boundary here is the combination of both, not either one alone.
What it still doesn't hide
It's worth being precise about the residual leakage, because "ECH hides which site you're visiting" oversells it slightly:
- Your IP address and the server's IP address are unencrypted, always. Traffic analysis based on destination IP, connection timing, packet sizes and flow patterns still works, and can often narrow down or confirm a guess about which site is behind a shared front even without the SNI.
- Certificates presented later in the handshake are already encrypted under TLS 1.3 regardless of ECH, so that part isn't new.
- A resolute observer with a large enough dataset can sometimes fingerprint a specific site behind a CDN by request timing and object sizes, the same techniques used against Tor hidden services for years.
- Plenty of middleboxes, corporate TLS-inspecting proxies and some national firewalls simply drop or reset connections carrying an unrecognised extension, so ECH adoption in the wild is patchy and browsers fall back to cleartext SNI when it's blocked, which rather defeats the purpose on those networks.
Browser support has been rolling out gradually behind feature flags and via provider-specific defaults rather than as a universal switch-flip, largely because of that middlebox breakage risk. The mechanism is sound and a real improvement over ESNI's design, but it's one layer in a stack, DNS privacy underneath it, traffic analysis resistance nowhere near solved above it, and it only helps as much as the crowd you're hiding in.