ssl_reject_handshake on 必须置于 http 块顶层且 Nginx ≥ 1.19.4 才生效,否则 IP 直连仍会暴露证书中的域名;因未匹配 server_name 的 HTTPS 请求会默认使用首个 ssl server 块的证书响应 ClientHello,导致扫描器通过证书 CN/SAN 提取真实域名和源站 IP。

必须把 ssl_reject_handshake on 放在 http 块顶层,且 Nginx ≥ 1.19.4;否则它不生效,IP 直连仍会吐出证书。
为什么默认配置会让 IP 直连泄露域名
Nginx 在收到 HTTPS 请求时,只要某个 server 块启用了 listen 443 ssl,哪怕没匹配上 server_name,也会用该块的 ssl_certificate 响应 ClientHello。扫描器(如 Censys)抓到这个证书,立刻能提取 CN 或 SAN 字段,反推出你的真实域名和源站 IP。这不是推测,是实打实的协议层泄露。
常见错误现象包括:
- 用
curl -v -k https://能看到完整证书链,Subject 明确写着你的域名your_server_ip - 在 Censys 搜索你的域名,结果里赫然出现你的服务器公网 IP
- CDN 回源未遮掩、或测试期间临时关闭 CDN,导致源站 IP 曾短暂暴露
正确配置位置与必要条件
ssl_reject_handshake 只在 HTTP 模块中起作用,且必须满足以下全部条件才真正拦截:
- 版本要求:
nginx -v输出至少为1.19.4(推荐1.25.5或更新稳定版) - 配置位置:只能写在
http { ... }块最外层,不能嵌套进server或stream - 监听要求:仅对含
ssl标志的listen生效,例如listen 443 ssl;纯 HTTP 或stream上下文无效 - 证书要求:所有启用
ssl的server块都必须提供有效的ssl_certificate和ssl_certificate_key,否则nginx -t直接报错
兜底 server 块必须显式声明并返回 444
即使启用了 ssl_reject_handshake,仍需一个明确的 default_server 来承接未匹配 SNI 的连接。否则 Nginx 可能 fallback 到第一个 SSL server 块,造成策略失效。
标准写法示例:
http {
ssl_reject_handshake on;
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/default.crt;
ssl_certificate_key /etc/nginx/ssl/default.key;
return 444;
}
}
关键点说明:
-
server_name _是 Nginx 官方约定的“非法主机名”写法,确保不意外匹配任何真实域名 -
default_server标识让该块成为 443 端口的唯一兜底入口 -
return 444是辅助保险:万一 TLS 握手被绕过(极罕见),HTTP 层也不返回任何响应体 - 证书路径可复用已有证书,也可单独生成轻量自签名(无需 CA 签发)
最容易踩的三个坑
实际部署中,这三个细节几乎必出问题:
- 误放在
stream块里:该指令在 stream 中完全无效,因为 stream 不解析 SNI,也无从判断“该拒哪个” - 已有
default_server未清理:如果你之前配了带证书的default_server但没开ssl_reject_handshake,它仍会照常发证书;启用后,该块行为才变为拒绝握手 - 用通配符
server_name *.example.com:这等于主动开放子域探测面,test.example.com这类请求会被接受,失去防护意义
真正起效的边界非常窄:它只在 ClientHello 后、证书发送前那一瞬间拦截。一旦证书已发出,就再也拦不住——所以配置位置、版本、兜底逻辑,三者缺一不可。











