Nginx 本身无内置 sni-routing 模块,“SNI Routing”是社区对基于 SNI 字段实现证书选择与反向代理转发的统称,核心依赖 OpenSSL 原生 SNI 支持、独立 server 块绑定专属证书、proxy_ssl_server_name on 透传 SNI 及 default_server 兜底机制。

直接说结论:Nginx 本身没有叫 sni-routing 的内置模块或指令,所谓“SNI Routing”是社区对基于 SNI 字段做证书选择 + 反向代理转发这一组合行为的通俗叫法,并非独立功能。真正起作用的是 Nginx 原生的 SNI 支持(依赖 OpenSSL)配合 proxy_pass 和 proxy_ssl_server_name on 实现的域名感知转发。
确认 Nginx 实际支持 SNI,而非仅“声称支持”
很多线上环境报错“证书错配”或 fallback 到第一个证书,根本原因不是配置写错,而是 Nginx 编译时链接的 OpenSSL 不支持 SNI,或版本太低(OpenSSL < 0.9.8j)。必须验证两点:
- 运行
nginx -V 2>&1 | grep -i openssl,输出必须含明确版本号(如OpenSSL 1.1.1w),且不能是BoringSSL - 检查 configure arguments 中是否含
--with-http_ssl_module;若出现TLS SNI support disabled,说明编译时未启用或 OpenSSL 不兼容 - 旧版 Nginx(如 1.10.2)即使带 OpenSSL 1.0.2,也需确认编译参数中显式指定了
--with-openssl=...路径,否则仍可能 fallback 到系统自带老旧 OpenSSL
每个 server 块必须独立绑定证书与域名,不可复用 ssl_certificate 指令
这是最常踩的坑:把多个 server_name 写在一个块里,或在不同块中重复使用同一组 ssl_certificate 路径。Nginx 启动不报错,但运行时只加载第一个块的证书,其余请求全走 fallback。
- 正确做法:每个
server块监听443 ssl http2,且server_name必须精确匹配目标域名(支持多域名,如server_name a.com www.a.com;) -
ssl_certificate和ssl_certificate_key路径必须指向该域名专属证书,不能共用/etc/letsencrypt/live/common.pem这类通配符路径(除非确实是通配符证书且域名匹配) - 若用 Let’s Encrypt,推荐为每个主域单独申请:执行
certbot --nginx -d a.com和certbot --nginx -d b.net,避免混用-d a.com -d b.net导致单证书覆盖多站
实现“SNI Routing”:用 proxy_pass + proxy_ssl_server_name 真正转发到后端
当你要把 HTTPS 流量按 SNI 域名分发到不同后端(比如 a.com → 10.0.1.10:443,b.net → 10.0.1.11:443),关键不是证书配置,而是反向代理层的行为控制:
- 必须在
location /块内启用proxy_ssl_server_name on,否则 Nginx 作为客户端连接后端时不会透传 SNI,后端无法选证 -
proxy_pass https://backend-a;中的backend-a必须是upstream定义,且不能写死 IP+端口(如https://10.0.1.10:443),否则 SNI 透传失效 - 示例 upstream 定义:
upstream backend-a { server 10.0.1.10:443; },然后proxy_pass https://backend-a; - 若后端也是 Nginx,需确保其也支持 SNI 并已配置对应证书——否则你只是把问题前移了一层
无 SNI 请求的 fallback 行为不可忽视
某些 IoT 设备、老 Android App 或定制固件发起 TLS 握手时不带 SNI 扩展。此时 Nginx 会无视所有 server_name,直接用监听该端口的第一个 server 块的证书响应——这会导致证书名称不匹配警告,甚至连接中断。
- 不要依赖“反正没人用老设备”;生产环境应显式定义一个兜底
server块,并设为default_server - 写法:
server { listen 443 ssl http2 default_server; server_name _; ssl_certificate /path/to/fallback.pem; ... } -
server_name _是 Nginx 特殊通配符,匹配任何未被其他块捕获的请求;default_server确保它优先被选为 fallback - 这个兜底证书可以是自签名,但必须能被客户端信任(或至少不弹严重错误),否则整个链路失败
真正难的不是写几行配置,而是在证书更新、后端扩容、客户端兼容性变化时,持续保证 SNI 字段从客户端发出→Nginx 解析→证书加载→代理透传→后端再解析这一整条链路不丢信息。任何一个环节 OpenSSL 版本不一致、参数缺失或日志沉默,都会导致“看起来配对了,但就是不生效”。


















