
图中先按监听地址和端口选择候选,再匹配名称,未命中才进入默认虚拟主机。
在配置里写上 server_name _,是不是就能拦住所有陌生域名?很多误解来自把一个约定俗成的占位名字,当成了带特殊语义的通配符。真正决定请求落到哪里的,是监听地址、端口、名称匹配,以及这些条件下的默认服务器。
先选监听范围,再选名字
想象同一个服务监听两个不同端口。一个请求进来时,先要落到对应的监听地址与端口范围内,才能在相关 server 配置之间继续匹配。不能因为某块配置里写了一个名字,就让所有其他端口的请求都被吸走。
在相应候选中,Nginx 根据请求中的服务器名称等信息寻找匹配项。找不到时,使用对应监听范围的默认服务器。如果没有显式指定,通常是该范围内的第一个 server。配置文件的排列顺序因此可能影响未匹配请求的归宿。
更稳妥的做法是显式声明 default_server,把默认行为从“碰巧排第一”变成可检查的意图。这个参数属于 listen 的选择,不是 server_name 的属性;在一个名称后面写再多下划线,也不能替代它。
下划线不是特殊拒绝符号
server_name _ 常用于给默认块放一个不打算实际使用的名称。它不表示任意 Host,也不会自动返回错误。若该块成为默认服务器,陌生域名进入它,是因为默认选择规则,而不是下划线匹配了所有字符串。
可以构造一个反例:默认块配置为返回自设状态,另一个普通块写 server_name _。请求一个完全不同的名字,仍应走默认块,而不是那个带下划线的块。改变默认块的位置或声明,结果才可能改变。这个反例能把名称和默认选择的作用分开。
还要区分空名称。server_name "" 用来描述缺少服务器名称的一类匹配情况,它与下划线不是同一种写法。缺少或畸形 Host 的请求,还可能受到 HTTP 解析规则限制,不保证总能进入你设想的普通业务处理路径。

默认块要有明确的处理动作
若希望未匹配请求被拒绝,应在默认块里配置相应的返回或关闭动作,而不是只设置一个名字。Nginx 的非标准 444 表示直接关闭连接,不是向客户端发送一个标准 HTTP 444 响应正文。也可以根据需求选择标准状态码,重点是明确客户端会观察到什么。
这与业务域名白名单有关,却不是完整的身份认证。攻击者仍可以发送允许的 Host,域名匹配不能替代用户登录、授权或上游服务的访问控制。默认块只是入口路由的一层规则,不能用它证明后台接口已受到完整保护。
涉及 IPv4、IPv6 或多个绑定地址时,需要分别检查实际生效的监听配置。不要在一个地址上验证了拒绝行为,就推断所有地址和端口都完全相同。
HTTPS 还有更早的选择阶段
HTTPS 在 HTTP 请求头出现之前先完成 TLS 握手,客户端可能通过 SNI 提供名称。证书选择和部分配置会更早发生,随后才有 HTTP Host 等处理。因此,一个仅针对明文 HTTP 的默认块示例,不能直接当作所有 HTTPS 场景的完整方案。
如果证书在握手阶段就不符合客户端预期,请求可能尚未到达你配置的 HTTP 返回动作。排查时应区分“连不上 TLS”“证书不匹配”和“HTTP 被拒绝”,不要把三个阶段都叫作 server_name 没匹配。
验证时一次只改一个条件
测试可以分别使用允许名称、陌生名称、不同监听端口和无名称场景,记录实际进入的块与客户端结果。执行前检查配置语法,并确认加载的是哪份配置;修改文件不代表运行中的进程已经读取新内容。
若没有启动真实 Nginx,只用程序模拟选择顺序,就只能验证教学模型,不能声称完成了服务配置实测。同样,返回状态相同也不一定说明命中了同一块,可以使用独立的测试响应标识定位,再从最终配置中去掉这些测试内容。
面试答题可以收束为:默认服务器属于监听范围,下划线只是普通名称,拒绝行为来自实际指令,HTTPS 还要分清握手与 HTTP 两个阶段。
如果删掉 server_name _ 之后,陌生 Host 仍然进入同一个默认块,这恰好说明哪一项配置才是决定因素?