How Encrypted Client Hello Hides Which Site You Visit
TLS 1.3 didn't hide which site you're visiting. Anyone watching your traffic can still see the hostname you're connecting to, even over a fully encrypted HTTPS connection.
Not the page, not the query string, but the domain name, 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.
Why SNI has to be sent in the clear
SNI exists because a single IP address can host thousands of TLS sites (think Cloudflare, or any shared hosting box). 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.
That hands a free list of every site you visit, timestamped, to:
- every network operator between you and the server
- every ISP
- every state-level censor
- every workplace proxy
All of that even though the actual page content is invisible to them.
The first fix, ESNI, leaked in several ways
This isn't a new problem. Encrypted SNI (ESNI) was drafted years ago as a fix, and it had flaws:
- It encrypted the SNI field but left the rest of the ClientHello's shape untouched.
- It published its encryption key in a DNS TXT record that itself became a fingerprint.
- It didn't handle the server not recognising the key the client used, which happened constantly during key rotation, and the fallback behaviour leaked information too.
Researchers found ways to correlate ESNI-using connections back to individual sites within weeks of it shipping in Firefox. ESNI was quietly withdrawn.
Encrypted Client Hello (ECH) is the redesign. The difference in approach explains both why ECH actually works and why it still needs a supporting cast to be effective.
ECH splits 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). That's 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+..."
From there the handshake goes:
- The client resolves that record and extracts the ECH configuration (key ID, HPKE cipher suite, public key).
- It uses that 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 and discovers the real SNI.
- It either serves the connection itself or hands it off internally to the right backend.
Key rotation no longer leaks 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. 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 to servers that publish no ECH config at all.
The point isn't cryptographic; it's about the shape of the traffic. If the 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.
The privacy comes from the crowd behind the cover name
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. 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, behind no CDN, gets essentially nothing from turning ECH on: the IP address alone already identifies it.
ECH is pointless without encrypted DNS
Fetching the HTTPS record over plain UDP port 53 puts the query name right back in plaintext for anyone watching. That cancels out the point of encrypting the ClientHello.
ECH only delivers on its promise paired with DNS-over-TLS, DNS-over-HTTPS, or an equivalent. The actual privacy boundary 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.