Caddy ECH 实操指南:把访客的访问域名藏进加密信封
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
上次线上抓包时我发现一件扎心的事:流量明明是 HTTPS,SNI 字段却是明文的——链路上任何 observer 都能看到你访客正在访问哪个站点。Caddy 的 ECH(Encrypted Client Hello)功能加密的正是这个暴露面。
它到底在解决什么:信封上的收件人地址
ECH 解决的问题只有一个:传统 TLS 握手里,客户端发出的第一个数据包(ClientHello)带着明文的域名(SNI),观察者偷看一眼就知道"谁在访问哪个站"。这个口子比想象的大:行业调研显示约92%的 Web 流量仍把 SNI 明文暴露。
最直观的理解是寄信。邮递员必须看到信封上的地址才能投递,传统握手就是一层信封,地址(SNI)大大咧咧写在外面。ECH 把它换成双层信封:
- 内层信封:写真实域名,叫inner ClientHello;
- 外层信封:写一个通用的"公共名称"(public name),叫outer ClientHello。
外层信封随 DNS 记录公布一份由 X25519 生成的公钥,客户端用它把内层信封加密后塞进外层;只有服务器能用私钥拆开外层、取出真实域名。邮递员(网络观察者)自始至终只看到那个公共名称。
这条路走了好几年,演进如下:
| 时间 | 名称 | 特点 | 局限 |
|---|---|---|---|
| 2018 | ESNI(RFC 8744) | 只加密 SNI 字段 | 浏览器支持撤掉后已废弃 |
| 2019–2022 | ECH 草案(draft-ietf-tls-esni) | 加密整个 ClientHello | 依赖特定 DNS 记录,部署复杂 |
| 2023+ | ECH + RFC 9460(HTTPS/SVCB 记录) | 发布机制标准化,浏览器默认支持 | 客户端需开启 DoH/DoT 才生效 |
Caddy 的实现位于modules/caddytls/ech.go,通过 TLS app 的encrypted_client_hello字段暴露,密钥的生成、存储、轮换全在 Caddy 内部自动完成,不需要你手写任何密钥。
⚡ 一句话记住:邮递员只能看到外层信封的地址;ECH 加密的是整个 ClientHello,而不只是 SNI。
从零跑起来:3 步让 ECH 上线
你只需要两样东西:一个自己控制的"外层信封"域名,和一个能写记录的 DNS 服务商。
✅ 动手前自检清单:
- ✅ Caddy ≥ 2.8.0(该版本开始支持 ECH)
- ✅ 有一个可解析的域名,且 A 记录已指向这台服务器
- ✅ 手里有能写记录的 DNS 服务商 API(如 Cloudflare),且域名 DNS 由它托管
- 🔧 第三方 DNS provider 不在官方构建里,需先用 xcaddy 构建:
xcaddy build --with github.com/caddyserver/dns-providers最小可运行 Caddyfile:
{ # 声明"外层信封"公共名称;密钥由 Caddy 自动生成并存储 ech public.example.com { dns cloudflare { # 用来发布 HTTPS 记录的 DNS 服务商 api_token {env.CF_API_TOKEN} } } } # 真实业务域名:观察者只会看到 public.example.com api.example.com, blog.example.com { reverse_proxy 127.0.0.1:8080 }caddy run之后,Caddy 替你干完三件事:生成 X25519 密钥对、为公共名称申请证书、把 ECH 配置写进每个受保护域名的 HTTPS 记录。
两条配置建议:
- 公共名称越少越好,通常一个就够。Caddy 源码注释里明确建议这么做:多个域名共用同一个外层名称,匿名集最大,流量分析最难。
- 公共名称选一个与业务无关的中性域名(自己买块"停车域名"即可),它的 DNS 也必须指向这台服务器——否则证书发不下来,客户端会被迫退回明文暴露真实域名,隐私反而更差。
⚡ 一句话记住:全局块里一行ech,出键、发证书、发记录全自动;公共名称要"少、中性、能解析"。
验证与排坑:3 层验证 + 3 个高频报错
按"本地 → 远程 → 浏览器"三层验证,全过了才算 ECH 真正生效。
📋 本地层:确认配置与密钥
caddy adapt --config Caddyfile # JSON 中应出现 encrypted_client_hello日志里看到generated new ECH config说明密钥已生成。
🌐 远程层:确认 DNS 发布结果
dig +short -t HTTPS api.example.com # 输出里应能看到 ech= 参数🖥 浏览器层:打开chrome://net-internals/#ech,访问一次站点,可以看到握手实际使用的外层名称。
三个高频问题,都是"现象 → 原因 → 解法":
- 现象:日志出现
domain does not have any existing records, so skipping publication。原因:该域名原本没有任何 DNS 记录,直接加 HTTPS 记录会破坏通配 A 记录的解析。解法:先确保域名已有 A/AAAA 记录。 - 现象:日志出现
domain has CNAME record, so unable to publish。原因:CNAME 与 HTTPS 记录互斥,无法共存。解法:把 CNAME 改成 A 记录再重启 Caddy。 - 现象:浏览器里仍是明文 SNI。原因:客户端没开 DoH/DoT,拿不到记录;或公共名称证书没发下来。解法:浏览器开启 DoH,并检查公共名称的 DNS 解析与 ACME 验证是否通过。
⚡ 一句话记住:服务器发布成功 + 客户端 DoH 开启,缺一不可,否则 ECH 会静默退回明文 SNI。
值不值得上
先给结论:开销小、维护成本几乎为零,但功能仍属实验阶段。数字说话:
- 性能开销:每次握手多一次 HPKE 解密,增加<1ms,体感为零;
- 维护成本:30 天自动轮换密钥,90 天后自动清理过期密钥,全程零手工;
- 协议代价:ECH 强制TLS 1.3,Caddy 会自动把连接最低版本抬上去;
- 兼容度:Chrome、Edge、Firefox 均已默认开启 ECH,覆盖约90%的浏览器流量,但客户端需启用 DoH/DoT 才生效。
一个诚实的提醒:源码里该功能标注着EXPERIMENTAL,配置结构未来可能调整;当前发布的记录 TTL 只有5 分钟,是现阶段的临时值。
适合:面向公网、在意"用户—域名"关联被分析,或域名本身敏感、属于隐私敏感行业的站点。不适合:纯内网服务,或 DNS 托管在无法写 HTTPS 记录的厂商上。
下次抓包时,你看到的明文 SNI 终于可以装进信封里了。建议先拿一个测试域名试水:配一行ech,发布后用一条dig验收。
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考