CloudFront + WebSocket + Caddy 排障实战

分类: 兴趣杂文 标签: CloudFront WebSocket Caddy 排障实战

CloudFront + WebSocket + Caddy 排障实战:从 502 到 TLS SNI 问题定位

在一次将 WebSocket 服务接入 CDN 的过程中,我遇到了一个典型但极具迷惑性的 CloudFront 502 Bad Gateway 问题。本文记录从配置到最终定位 TLS SNI 不匹配 的完整排查过程,希望对类似架构有参考价值。


一、架构说明

整体架构如下:

客户端
   ↓
CloudFront(CDN)
   ↓
源站(Caddy + WebSocket 服务)

源站域名(示例):

media.example.com

CloudFront 使用其默认分配域名:

xxxxx.cloudfront.net

目标是通过 CloudFront 访问后端 WebSocket 服务。


二、CloudFront 配置要点

1. Origin 配置

  • Origin Domain:media.example.com
  • Protocol:HTTPS Only
  • HTTPS Port:443
  • Minimum TLS:TLSv1.2

2. Cache / Policy

  • Cache Policy:CachingDisabled
  • Origin Request Policy:AllViewerExceptHostHeader

3. Viewer 端

  • Viewer Protocol Policy:HTTPS Only
  • 支持 WebSocket(WSS)

三、初始问题:502 Bad Gateway

访问 CloudFront 域名时返回:

502 Bad Gateway
We can't connect to the server for this app or website at this time.

但源站直接访问完全正常:

curl https://media.example.com
# 返回正常 200

四、第一轮排查:协议与重定向

确认:

  • CloudFront Origin 使用 HTTPS ✅
  • 源站未返回 HTTP → HTTPS 重定向影响 ❌

结论:不是 308/301 重定向问题。


五、第二轮排查:网络连通性

在源服务器执行抓包:

tcpdump -i any port 443 -n

结果显示:

  • CloudFront 节点 IP 可以成功建立 TCP 连接 ✅
  • TLS ClientHello 已发送 ✅
  • 源站返回 TLS Alert 并立即断开 ❌

关键现象:

ClientHello → Server
Server → TLS Alert → 关闭连接

说明问题发生在 TLS 握手阶段


六、关键抓包分析(核心突破点)

进一步抓取 TLS 内容:

tcpdump -i any port 443 -n -X

在 ClientHello 中可以看到 SNI 扩展:

Server Name Indication (SNI):
dxxxxxxxxxxxx.cloudfront.net

注意:

  • CloudFront 回源时使用的 SNI 是 CloudFront 自身域名
  • 而不是你的源站域名 media.example.com

七、根本原因

❗ TLS SNI 不匹配

源站 Caddy 配置监听的是:

media.example.com

但 CloudFront 实际发起 TLS 时:

SNI: xxxxx.cloudfront.net

导致:

  • Caddy 无法匹配对应站点
  • TLS 握手失败
  • 返回 Alert(通常为 internal_error 或 handshake failure)
  • CloudFront 收到异常 → 返回 502

八、为什么 Host 不起作用?

即使:

  • HTTP Header 中 Host 是 media.example.com

但在 TLS 层:

SNI 在 TLS 握手阶段决定证书匹配,早于 HTTP 请求

也就是说:

TLS 连接还没建立成功,就已经失败了


九、解决方案

方案一(推荐):配置 fallback SNI

在 Caddy 中允许 fallback:

{
    servers {
        protocols h1 h2
    }
}

media.example.com {
    tls {
        # 确保支持 SNI fallback
    }

    reverse_proxy 127.0.0.1:10000
}

或使用 Caddy 默认自动证书机制,确保能匹配任意 SNI。


方案二:让 CloudFront 使用正确 SNI(不可控)

CloudFront 无法修改 SNI,这是其设计限制:

回源 SNI = Origin Domain / CloudFront 内部域名

因此不能从 CloudFront 侧修正。


方案三:使用泛域名证书

确保源站证书满足:

  • 支持 media.example.com
  • 或 wildcard:*.example.com

并确保 Caddy 可以匹配请求域名。


方案四:关闭严格 SNI 绑定(关键)

在 Caddy / Nginx 中避免强依赖 SNI 精确匹配:

  • 使用默认站点 fallback
  • 或配置 catch-all server block

十、最终验证

修复后再次访问:

https://xxxxx.cloudfront.net

结果:

  • TLS 握手成功 ✅
  • CloudFront 返回 200 ✅
  • WebSocket 正常升级 ✅

十一、排查经验总结

1. 502 不一定是网络问题

CloudFront 502 常见原因:

  • TLS 握手失败
  • SNI 不匹配
  • 证书不兼容
  • 源站拒绝连接

2. 抓包是关键手段

推荐命令:

tcpdump -i any port 443 -n -X

重点观察:

  • ClientHello 中的 SNI
  • Server 返回的 TLS Alert
  • 是否完成握手

3. 排查路径建议

  1. curl 直连源站
  2. tcpdump 看 TCP 是否通
  3. 分析 TLS ClientHello
  4. 检查证书与 SNI
  5. 查看服务器日志
  6. 排除 CloudFront 配置

十二、结论

本次问题的本质是:

CloudFront 回源使用的 SNI 与源站证书/服务域名不匹配,导致 TLS 握手失败,从而引发 502 Bad Gateway。

这一类问题具有典型特征:

  • TCP 正常
  • TLS 失败
  • 服务器日志可能无记录
  • 502 快速返回

如果你在类似架构中遇到 CloudFront / Cloudflare / Caddy / WebSocket 组合问题,建议优先检查:

SNI + TLS 握手 + 证书匹配

这是最容易被忽略但最关键的一环。


如果你需要,我可以再帮你把这篇文章再升级成「生产级排障 checklist + 标准 SOP」,用于你后续复用。