ssl_reject_handshake on必须置于http块顶层,因nginx默认对ip直连tls请求返回首个ssl server证书致域名泄露;仅当版本≥1.19.4、所有ssl监听server均配合法证书且无冲突default_server时才生效。

必须用 ssl_reject_handshake on,且只在 http 块里配,否则完全不生效——这不是可选项,是硬性前提。
为什么直接 IP 访问 HTTPS 会泄露证书
当扫描器(比如 Censys)向你的服务器 IP 发起 openssl s_client -connect 1.2.3.4:443 请求时,Nginx 默认会把第一个 listen 443 ssl 的 server 块里的证书发出去。哪怕你没配 server_name,哪怕这个 server 是给 example.com 用的,它也会“递出”证书。证书里的 CN 或 SAN 字段一暴露,域名和源站 IP 的绑定关系就进了公开测绘库。
这不是配置错误,是 Nginx 的默认行为。想靠 return 444 或 deny all 拦在 HTTP 层?晚了——TLS 握手已经完成,证书早就发完了。
正确启用 ssl_reject_handshake 的三要素
这个指令不是加了就管用,必须同时满足:
-
nginx -v输出版本 ≥1.19.4(生产环境建议用1.25.5或更新稳定版) - 配置位置必须是
http { ... }块顶层,不能只写在某个server里,也不能放在stream块中(stream不解析 SNI,该指令在那里无效) - 所有启用了
ssl的listen(如listen 443 ssl)所归属的server块,都必须有合法的ssl_certificate和ssl_certificate_key,否则 Nginx 启动失败
示例有效写法:
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;
}
}
常见踩坑点:看似配了,实则白配
这些情况会导致 ssl_reject_handshake 形同虚设:
- 把指令误写进
stream { }块——它只在 HTTP 模块起作用,stream是四层代理,根本不看 SNI - 已有带证书的
default_server但没加ssl_reject_handshake on,启用后必须确认该server块仍保留有效证书路径,否则启动报错:SSL_CTX_use_PrivateKey_file() failed - 用通配符
server_name *.example.com当兜底——这等于主动接受所有子域探测,ssl_reject_handshake对匹配上的请求完全不触发 - 忘记删掉旧的、没开此指令的
default_server——Nginx 会按配置顺序选第一个可用的 SSLserver,旧块还在,新防护就绕过去了
验证是否真生效:别信日志,要抓包
日志里看不到握手被拒的记录(因为连接在 TLS 层就被切断),得用 OpenSSL 实测:
执行:openssl s_client -connect your.ip.address:443 -servername evil.test.com -tls1_2 2>&1 | grep "Verify return code"
如果返回 Verify return code: 0 (ok) 或出现完整证书链,说明没生效;如果卡在 CONNECTED(00000003) 后直接断开、无证书输出,或报 SSL routines:tls_process_server_hello:tlsv1 alert unrecognized name,才是成功。
真正难的是让所有 HTTPS server 块都收敛到统一的 SNI 白名单策略——漏掉一个没配证书或没列进 server_name 的生产域名,就等于留了个后门。











