Every TLS connection your platform terminates starts with a confession. Before any encryption kicks in, the client sends a ClientHello in plaintext — and inside it sits the Server Name Indication (SNI), the exact hostname the visitor is trying to reach. Anyone watching the wire between your users and your load balancer can read it: not the page content, not the credentials, but precisely which tenant app each connection is headed to.
On a self-hosted PaaS where dozens of tenant custom domains share one load-balancer IP, that leak is structural. The IP address tells an observer "someone is talking to this platform"; the SNI tells them "and here is exactly whose app they're opening." Encrypted Client Hello (ECH) exists to close that gap, and this year the operational story finally caught up with the cryptography: Caddy 2.11.1, released in February 2026, rotates ECH keys automatically, and the IETF published ECH as RFC 9849 a month later. The question is no longer whether the mechanism works — it's what wiring it into a multi-tenant TLS front actually takes, and what's still missing before it's real privacy rather than defense in depth.
The whole change in ten lines
Here is the core deliverable up front: the Caddy configuration that turns ECH on for a multi-tenant front, and what automatic rotation changed about operating it.
{
email ops@example.com
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
ech ech.example.com
}
tenant-a.com, tenant-b.com, *.apps.example.com {
reverse_proxy 127.0.0.1:9000
}Three pieces do the work. The dns global option plugs in a DNS provider module (built into your Caddy binary via xcaddy) so Caddy can publish records on your behalf. The ech global option names the public name — the outer, provider-level placeholder identity that every ECH connection advertises in plaintext instead of the real tenant hostname. And Caddy handles the rest: it generates the ECH key pair, publishes the ech parameter into the HTTPS-type DNS records for the domains it manages, and serves the inner, encrypted handshake.
The part Caddy 2.11.1 automated is the key lifecycle. Before this release, ECH keys were generated but rotating them was a manual operation: generate a new key, publish the replacement config to DNS, wait out TTLs, retire the old key — a small key-management ceremony that had to be run on a schedule, forever, to keep the privacy guarantee meaningful. Stale keys are worse than no keys in one respect: they train operators to treat ECH as set-and-forget while the cryptographic freshness silently decays. Automatic rotation removes that ceremony. Caddy now rolls keys and republishes configs behind the scenes, which was the last operational reason to leave ECH off a production front. Combined with 2.11's time-rolling log options and SIGUSR1-triggered reloads, the release reads as a coherent "run this unattended" package.
One honest caveat before the background: Caddy can only publish ECH configs for zones its DNS module controls. If your tenants bring custom domains whose DNS lives at a registrar you can't write to, automatic publication stops at your zone boundary — a gap the "What's still missing" section below takes up directly.
Why shared IPs make this a PaaS problem
To see why ECH matters more to a platform than to a single site, follow one connection. A visitor opens tenant-a.com in a browser. DNS resolves it to your load balancer's IP — the same IP that also serves tenant-b.com and fifty other tenant apps. The browser opens a TCP connection and sends its ClientHello with SNI tenant-a.com, in the clear. Your reverse proxy reads the SNI to pick the right certificate and route the request. So does every passive observer on the path: the visitor's ISP, the datacenter's upstream, anyone with a tap.
With ECH, the ClientHello splits in two. The outer hello carries only the public name — ech.example.com — which is identical for every tenant behind that front. The real SNI rides inside an inner hello encrypted with HPKE under a public key the client fetched from the tenant domain's HTTPS DNS record. The observer sees a connection to ech.example.com and learns nothing about which tenant app it carries. Your Caddy front decrypts the inner hello, recovers tenant-a.com, and routes exactly as before.
This is why the multi-tenant shape is the point. A single site on a dedicated IP leaks little through SNI: the IP already identifies the destination. But a PaaS concentrates many hostnames behind few IPs precisely to make IP-based identification useless — and then hands the mapping straight back through the SNI. ECH restores the anonymity set the shared IP was supposed to provide: from the wire's perspective, every tenant connection looks the same.
There is a subtlety worth naming. ECH does not hide that a connection goes to your platform, nor its timing and size. Traffic-analysis techniques can still fingerprint destinations from flow metadata. What ECH closes is the cheap, exact, passive readout — the SNI sniffer that turns a packet capture into a per-tenant visit log with no cryptanalysis at all.
What actually changed: the 2025–2026 ECH timeline
ECH spent years as a draft that browsers shipped behind flags while servers waited for final text. That logjam broke in about eleven months:
| Milestone | Date | Why it matters to a PaaS |
|---|---|---|
| Caddy 2.10 ships ECH | April 2025 | First turnkey server support: set a public name plus DNS provider, Caddy generates and publishes configs |
| Caddy 2.11.1 automates key rotation | February 2026 | Removes the manual rekey/republish ceremony; ECH becomes operable unattended |
| ECH published as RFC 9849 (+ RFC 9848 for DNS carriage) | March 2026 | Draft-ietf-tls-esni finally Standards Track; vendors can ship without tracking draft churn |
| OpenSSL 4.0 ships ECH | April 2026 | The TLS library most servers link against now speaks ECH; nginx/HAProxy-class adoption unblocked |
| Go 1.24 native ECH support | 2026 | Caddy's own stack (and Go reverse proxies generally) get first-class ECH without forks |
| Chrome 117+/119, Firefox 118+/119 | 2023–2024 onward | The two engines covering the large majority of traffic negotiate ECH by default where offered |
| Android 17 hides destinations at OS level | August 2026 | Closes the SNI gap below the browser for mobile traffic |
Two things in that table deserve emphasis. First, the client side has been ready longer than most operators realize: Chrome and Firefox have negotiated ECH by default for over a year, so enabling it server-side immediately covers most browser traffic — there is no "wait for clients to upgrade" phase left for the mainstream web. Second, RFC 9849 plus OpenSSL 4.0 changes the adoption curve from "Caddy and Cloudflare" to "whoever upgrades their TLS stack," which is what turns ECH from a differentiator into table stakes over the next upgrade cycle.
How ECH hides the SNI in five steps
The mechanism is simpler than the acronym soup suggests. Walking through one connection:
- Publish. The platform publishes an ECH config — public name, cipher suites, and an HPKE public key — in the
echparameter of each tenant domain's HTTPS DNS record. In Caddy's model this happens automatically through the DNS provider module. - Fetch. The client resolves the tenant domain, gets the HTTPS record (ideally over DNS-over-HTTPS or DNS-over-TLS — the "What's still missing" section explains why that's load-bearing), and learns ECH is available plus which key to use.
- Split. The client builds two hellos: an inner one with the real SNI (
tenant-a.com), encrypted under the published key, and an outer one addressed to the public name (ech.example.com) carrying the encrypted blob as an extension. - Terminate. Caddy receives the outer hello, decrypts the inner one with its private key, and proceeds with the handshake as if the client had sent
tenant-a.comdirectly — same certificate selection, same routing, same backends. - Rotate. On its schedule, Caddy generates a fresh key pair, publishes the new config to DNS, and phases out the old key after TTLs expire. Since 2.11.1, no operator action is involved.
Note what the platform operator never touches: no per-tenant key material, no handshake code, no certificate changes. The moving parts are DNS publication rights and the rotation schedule — both now automated for zones Caddy manages. The remaining work is all at the boundary: getting every tenant domain's HTTPS record published and keeping clients fetching them over encrypted DNS.
What's still missing before it's real privacy
Automatic rotation closed the server-operations gap. Four gaps remain, and they decide whether ECH on your front is a genuine privacy guarantee or a checkbox:
| Gap | Status in late 2026 | What it takes to close |
|---|---|---|
| Client coverage | Chrome, Firefox, Edge negotiate by default; Safari support still limited/in development | Nothing to do but track it — the gap is Apple's release train, not your config |
| Encrypted DNS on the client | ECH without DoH/DoT leaks the hostname in the DNS query itself | Can't be fixed server-side; browsers increasingly default to DoH, and Android 17 now closes it at OS level — measure, don't assume |
| DNS publication for BYO domains | Caddy publishes only where its DNS module has write access; tenant custom domains at external registrars are outside that boundary | Either require tenants to delegate an _ech-capable zone or CNAME to platform-managed DNS, or publish configs out-of-band and accept manual rotation there |
| Verification tooling | GREASE ECH (fake extensions sent for middlebox compatibility) means "extension present" proves nothing; dedicated scanners like echcheck only appeared in 2026 | Verify by observing the public name — not the real SNI — in captured ClientHellos, not by the mere presence of an ECH extension |
The BYO-domain row is the one that bites platforms specifically. The tenants whose privacy matters most — custom domains on your shared IPs — are exactly the tenants whose DNS you don't control. There are three honest postures: restrict ECH coverage to platform-subdomain traffic (*.apps.example.com) where you own the zone; ask custom-domain tenants to CNAME to a platform-managed name so your HTTPS records (and ECH configs) apply; or document ECH as best-effort for BYO domains until DNS-provider coverage or delegated publication closes the loop. What you can't do is claim "ECH for every tenant" while half your custom domains lack published configs — clients can't use what DNS doesn't advertise.
Should you turn it on this quarter?
For a Caddy-fronted platform, the decision is now lopsided in ECH's favor — with one scoping decision attached.
Turn it on for every zone Caddy manages. The cost is a public name, a DNS provider module in your build, and one global option; the benefit is that the majority of your browser traffic immediately stops broadcasting per-tenant hostnames to passive observers. There is no client migration, no certificate reissue, no routing change. Rotation — the only recurring operational cost — is now automatic. This is the rare privacy feature with near-zero marginal cost once the front supports it.
Scope the claim honestly. Announce ECH for platform-managed domains, treat BYO custom domains as "covered where DNS delegation permits," and put the delegation story on the roadmap rather than in the marketing. Then verify instead of trusting: capture a handshake with tshark or run a dedicated ECH scanner against a tenant domain and confirm the wire-visible SNI is your public name, not the tenant's. If you see the real SNI, either the client didn't fetch a config (check the HTTPS record) or it sent GREASE (check with a second client) — both are diagnosable in minutes.
Over the next upgrade cycle, ECH stops being a decision at all. With RFC status settled, OpenSSL and Go carrying native support, and browsers negotiating by default, the platforms that enabled it early will simply have a quieter wire — and the ones that didn't will be explaining why their tenants' hostnames are still readable in every packet capture.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



