服务器和代理

no "ssl_certificate" is defined:Nginx 默认站点没配证书

作者: · 最后核实

报错信息no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: 203.0.113.10, server: 0.0.0.0:443

这条错误的意思是:一个 TLS 客户端连到了 SSL 端口上某个没配 ssl_certificate 的 server 块,Nginx 没有证书可出示,只好中断握手。 多数情况下这个 server 块就是 443 端口的默认站点,它会接收用 IP 访问、不带 SNI 或访问未配置域名的连接。

快速修复

修法是给没有证书的 server 块补上证书,或者让它拒绝 TLS 握手。先列出 443 端口上的所有 server 块,看哪个没有证书:

sudo nginx -T 2>/dev/null | grep -nE "server_name|listen .*443|ssl_certificate |default_server"

然后要么给这个 server 块配证书,要么让兜底 server 明确拒绝 TLS 握手(Nginx 1.19.4 及以上):

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;
}

执行 sudo nginx -t && sudo systemctl reload nginx。用 SSL 检测工具可以看到每个域名现在拿到的是哪张证书。

原因

最常见的是 443 端口的默认 server 没有证书(原因 1 和 2),其余 3 种是证书指令写错了地方。

1. 兜底或默认 server 没有证书

只要有一个 server 写了 listen 443 ssl,同一地址和端口上的所有 server 都要处理 TLS。从 80 端口模板复制来的 server_name _; 兜底块,或者发行版自带的默认站点,往往没有证书。

  • 确认: 报错里的 server: 是 0.0.0.0:443 或 [::]:443,nginx -T 里能找到该端口上没有 ssl_certificate 的 server 块。
  • 修复: 给它加 ssl_reject_handshake on;,用不到的话直接删掉。

2. 客户端不发送 SNI,或直接用 IP 访问

扫描器、健康检查、很老的客户端连接时不带 SNI,Nginx 无法选出对应的站点,只能交给默认 server。

  • 确认: openssl s_client -connect 203.0.113.10:443 -noservername 失败,加上 -servername example.com 就正常。
  • 修复: 如果确实希望 IP 访问也落到某个站点,就把它标成 default_server;否则保留拒绝握手的兜底块。负载均衡(如阿里云 SLB、腾讯云 CLB)的健康检查改成带域名的 HTTP 检查,或者检查后端的 80 端口。

3. 证书指令写错了位置

ssl_certificate 只能写在 http 或 server 里,不会从别的 server 块继承,也不能写在 location 里。

  • 确认: 证书只写在了同名的另一个 server 块里,或者写在一个没有被 include 的文件里(nginx -T 看不到它)。
  • 修复: 把指令挪到 server_name 对应的那个 server 块,或者在那里 include 进来。

4. 同一个域名有两个 server 块

Certbot 的 Nginx 插件之类的工具只改其中一个块。如果 443 上有两个相同 server_name 的块,Nginx 只用第一个,并给出警告。

  • 确认: nginx -t 打印 conflicting server name "example.com" on 0.0.0.0:443, ignored。
  • 修复: 合并成一个块,同一个域名和端口只保留一处证书配置。

5. 证书路径里用了变量

Nginx 1.15.9 起 ssl_certificate 可以包含变量。变量解析为空或文件不存在时,这些域名的握手会失败。

  • 修复: 检查设置变量的 map,并提供默认值。

各环境修法

这条日志在各环境里都指向一个没有证书的 443 server 块,补上证书或加 ssl_reject_handshake on; 即可。

宝塔面板: 一个站点关闭 SSL 后,其配置里如果还残留 listen 443 ssl,或者手动改过默认站点配置,就会出现这条日志。在站点设置里重新保存一次 SSL 配置,或在“网站 → 默认站点”中指定一个已配好证书的站点,步骤见 宝塔面板配置 SSL。

Debian / Ubuntu 软件包: /etc/nginx/sites-enabled/default 默认只监听 80,但被改过的副本常常加了 443。禁用它,或者加上 ssl_reject_handshake on;。

Docker / Nginx Proxy Manager: 自动生成的默认 server 可能没有证书,在代理设置里配置默认证书或拒绝握手的默认站点。

Kubernetes ingress-nginx: 控制器会给未知主机名返回自带的假证书,所以很少出现这条日志,未知域名看到证书不匹配是正常的。

Nginx 1.19.4 之前的版本: 没有 ssl_reject_handshake,可以给兜底块配一张自签名证书,并 return 444; 直接断开连接。

不要这样做

这些做法会制造新问题,或者漏掉实际的故障。

  • 把真实站点的证书挂在兜底 server 上应付所有未知域名。 走错站点的访客会看到证书不匹配,扫描 IP 的人还能借此知道你有哪些域名。
  • 从 listen 里去掉 ssl 让报错消失。 端口会用明文 HTTP 回应 TLS 客户端,所有 HTTPS 访问都会失败。
  • 完全无视这条日志。 多数时候是噪音,但正式站点在改配置时丢了证书,也会报同样的错。

常见问题

网站访问正常,日志里却一直刷这个错,要紧吗?

一般不要紧。你的站点自己有证书,但扫描器和爬虫用 IP 或乱填的域名访问,落到了没有证书的默认 server 上。对正常访客无害,只是日志噪音。加一个写了 ssl_reject_handshake on; 的兜底 server 就不会再刷。

可以把 ssl_certificate 写在 http 块里吗?

可以。http 块里的指令会被所有没单独设置的 server 继承,相当于给每个 SSL server 一张兜底证书。但访问错站点的客户端会看到域名不匹配,而不是握手失败。

国内云服务器上,用 IP 访问 443 会有什么额外问题?

未备案域名解析到国内云服务器时,80/443 端口的访问可能被云厂商拦截,这和本报错无关。用 IP 访问 HTTPS 本来就拿不到匹配的证书,兜底 server 直接拒绝握手是最干净的做法。

资料来源

  1. nginx ngx_http_ssl_module (ssl_certificate, ssl_reject_handshake)
  2. nginx: Configuring HTTPS servers