签名 HTTPS 报错 TLS 握手失败的终极解法
在使用 Caddy 为纯 IP 服务器(如内网环境或测试服)配置自签名 HTTPS 时,很多人会发现即使配置了 tls internal,浏览器依然无法访问,后台日志疯狂报错 TLS handshake error。本文将分析该问题的底层原因,并提供最简的解决方案。
表现症状
当你通过浏览器访问纯 IP 的 HTTPS 站点时,页面提示连接被重置或 SSL 协议错误。查看 Caddy 日志,会出现如下关键报错:
Plaintext
"msg":"no matching certificates and no custom selection logic","identifier":""
"msg":"http: TLS handshake error ... no certificate available for '<你的服务器IP>'"
为什么 tls internal 会失效?
核心原因在于 SNI(服务器名称指示)丢失。
在 HTTPS 握手阶段,SNI 用来告诉服务器客户端想访问哪个域名,以便服务器返回对应的证书。然而,当浏览器直接请求纯 IP 地址时,底层协议不会发送 SNI。
Caddy 接收到这类“没带名字”的请求后,因为拿不到明确的标识符,无法在自己的配置库里匹配到那张 tls internal 生成的自签名证书,最终只能因为找不到证书而直接中断连接。
解决方案:使用 default_sni 兜底
既然浏览器不发 SNI,我们只需在 Caddy 的全局配置中,强制把所有无 SNI 的请求都默认当作对本机 IP 的请求。同时,站点块的名称必须显式绑定这个具体的 IP,绝不能使用 https://:8443 这种简写。
终极 Caddyfile 配置模板
修改你的 Caddyfile,参考以下标准结构(请将 <你的服务器IP> 替换为你实际访问的公网或局域网 IP):
Plaintext
{
# 核心救命稻草:强制将没有 SNI 的瞎请求,全部指定为该 IP
default_sni <你的服务器IP>
}
# 必须显式写出完整的 IP 和端口,与 default_sni 完全一致
<你的服务器IP>:8443 {
# 启用 Caddy 内部自签名证书
tls internal
# 你的业务逻辑,比如反向代理
reverse_proxy localhost:5244
}
# 多个端口可以并行配置
<你的服务器IP>:9443 {
tls internal
reverse_proxy localhost:3001
}
# 80 端口 HTTP 服务不受影响,按需配置
:80 {
root * /usr/share/caddy
file_server
}
修改完成后,重载配置即可:
Bash
caddy reload --config Caddyfile
总结
配置生效后,浏览器再通过纯 IP 访问时,TLS 握手就能顺利完成。由于使用的是自签名证书,浏览器仍会提示“您的连接不是私密连接”,点击“高级 -> 继续访问”即可正常打开网站。
💡 进阶建议: 如果你的服务器开放了外网的 80 和 443 端口,并且你希望彻底消除浏览器的“不安全”红锁警告,可以考虑将配置中的 IP 直接替换为 <你的服务器IP>.nip.io,并删掉 tls internal 这一行。Caddy 会自动利用这个魔法域名为你申请真实的 Let’s Encrypt 公网证书,实现全自动的绿色安全锁。