☰
Nginx server_name配置指南:匹配优先级、正则与排错实战
2026/10/12 8:19:17 网站建设 项目流程

大约半年前,我在调试一组 Nginx 配置时,遇到一个特别诡异的现象:明明把配置文件写得清清楚楚,每个站点的 server_name 都填对了,可请求就是跑到错误的站点上。抓包看请求头,Host 完全正确,配置 reload 也执行了,愣是排查了很久才发现问题居然出在一条“配置指令-server_name”的细节上——一个正则没写对,导致匹配优先级发生了我完全没预料到的变化。

在 Nginx 的整个配置体系里,server_name 这条指令看似简单,实际上却是最容易让新手甚至老手翻车的地方。它是虚拟主机识别的核心,决定了进入服务器的请求会被哪一个 server 块处理,直接影响你部署的每一个网站、每一个反向代理能否被正确路由。这篇内容我不打算给你念官方文档,而是把我在实际运维中踩过的坑、总结出的匹配规则,以及排查思路完整地讲一遍,希望能帮你少走弯路。

1. server_name 到底在做什么:虚拟主机识别的第一道关卡

很多人把 server_name 当成是把“域名绑定到站点”的纯声明式配置,觉得只要填上域名就万事大吉。真要这么理解,后边的坑会一个接一个。

1.1 一条配置指令,决定请求的“归属权”

服务器的核心任务之一,就是在一台机器上同时承载多个网站。当用户输入blog.example.com时,浏览器会带着Host: blog.example.com这个请求头找到你的服务器 IP。但服务器收到请求后,怎么知道该把这条请求交给blog这个站点的代码,而不是交给shop或api的代码呢?

答案就是靠这条“配置指令-server_name”。Nginx 会拿请求头里的 Host 字段,和每个 server 块中声明的 server_name 进行匹配。匹配上了,就用那个 server 块里的 root、proxy_pass、SSL 证书等配置;匹配不上,就落到默认的 server 块。

我习惯把这个过程类比成小区物业的“门禁系统”:每个 server 块就是一个独立的住户,server_name 就是门禁系统里登记的业主姓名。访客来了,报上“我是来找张三的”(Host 头),门禁系统(Nginx)就去比对业主名册(配置文件),找到张三那一户,让访客进去。要是名册里没有这个人,就走默认流程——通常是扔到第一个配置的 server 块,或者返回一个错误页。

这个类比能很好地解释很多现象:为什么你把新域名加进去之后 reload,新请求仍然落到旧站点上?大概率是名册里登记的姓名(server_name 写法)跟访客报的名字(Host 头)没有完全匹配上。

1.2 从一条请求的完整旅程看 server_name 的位置

想要真正吃透这条“配置指令-server_name”的威力,得把一条请求从进入到返回的完整路径看清楚。请求进来之后,Nginx 处理流程大致是:

  1. 先解析 TCP 连接,如果是 HTTPS,则先完成 TLS 握手,客户端在握手期间发送 SNI(Server Name Indication),这个 SNI 字段就是域名。
  2. Nginx 根据 SNI 初步嗅探可能匹配的证书和 server 块。
  3. HTTP 请求头到达后,Nginx 提取 Host 字段,开始最终的 server_name 匹配。
  4. 命中某个 server 块后,再根据该 server 块内的 location 指令继续分流,决定是交给静态文件、PHP-FPM 还是反向代理后端。
  5. 返回响应。

所以要注意,server_name 匹配是发生在 location 匹配之前的关键步骤。如果你连 server 块都选错了,后边 location 写得再漂亮,也全都跑偏。

这里有个经常被忽略的细节:HTTPS 场景下的 SNI 与 Host 头。HTTPS 握手发生在 HTTP 请求发送之前,Nginx 在握手阶段只能靠 SNI 来选证书。正常情况下 SNI 里带的域名会和 Host 头一致,但由于某些老客户端或特殊网络环境,SNI 可能是空的,或者只带了 IP。在这种情况下,Nginx 会基于 IP 匹配默认 server 块来拿证书,然后就容易引发证书不匹配的报错。这种问题的根子就在 server_name 的配置策略上——怎么确保默认 server 拿到的证书能兜底。

2. 配置指令-server_name 的完整语法:从静态域名到通配符

Nginx 的 server_name 写法非常灵活,但灵活性也是出错的重灾区。我把常见写法拆开来看,每一种背后对应的都是不同的业务需求。

2.1 静态写法、泛解析写法与正则写法

第一种是静态写法,最简单也最直观:

server { listen 80; server_name www.example.com; }

这种写法要求 Host 头必须是www.example.com才能精确命中,少一个 www、多个标点都不行。适合域名固定的独立站点。

第二种是通配符写法,用*匹配开头的子域名:

server { listen 80; server_name *.example.com; }

这一段配置的意思是,任何abc.example.com、blog.example.com之类的子域名都能命中。需要注意,通配符有严格的边界规则:*.example.com不匹配example.com本身(即裸域),也不匹配a.b.example.com这种多级子域名。

与之对应,Nginx 还支持一种省略写法,比如写server_name .example.com;,等价于既匹配example.com也匹配*.example.com。这个前置点号是个很实用的技巧,但我发现很多人并不知道这个语法,结果写了两行 server_name 来实现同样效果,徒增维护成本。

第三种是正则写法,用~开头:

server { listen 80; server_name ~^(?<sub>.+)\.example\.com$; }

正则写法能解决通配符解决不了的问题——比如匹配任意子域名同时要捕获子域名内容,比如区分www和非www,再比如匹配类似prod-123.example.com这种带编号的域名。正则写法威力大,但这也是全文最容易出错的地方。我踩过的那个坑,本质上就是正则里漏了一个转义符\.没写,导致点号被当作“任意字符”去匹配,匹配范围被奇怪地扩大,请求落在了错误的虚拟主机上。

2.2 默认站点与 IP 访问的处理

实际部署中,你至少会遇到两种额外的访问来源:一是直接用 IP 访问服务器,二是域名根本没匹配上任何规则。这两种请求该交给谁?答案是默认 server 块。

在 Nginx 中,你可以通过default_server参数来显式指定默认站点:

server { listen 80 default_server; server_name _; return 444; }

这里的server_name _;是一个历史上流传下来的惯例写法,_并不是真的需要匹配什么,它只是一个永远不会跟正常域名冲突的占位符。配合default_server后,所有找不到匹配域的请求都会落到这个块里来。我建议生产环境的服务器一定要配置这样一个默认拒绝站点,否则 IP 扫描、未知域名的恶意请求就会落进你的某个业务站点,轻则返回错误信息泄露细节,重则被探测出后台地址。

注意,如果没有任何一个 server 块声明default_server,Nginx 会默认把 listen 同端口、配置顺序最靠前的那个 server 块当作默认块。这就是一个常见的困惑来源:明明某个站点配置写在第一个,另一台设备用 IP 一访问,看到的却是那个第一个站点的页面。原因就在这里。

2.3 一个 server 块配置多个域名

一个 server 块配置多个域名的情况同样常见——比如同一套站点的 www 域和裸域共用同一个代码目录。这时候可以用空格分隔多个名称:

server { listen 80; server_name example.com www.example.com api.example.com; root /var/www/example; }

这个配法的好处是清晰、容易维护。三个域名解析过来都是同一套业务代码,SSL 证书如果是一张多域名证书也能直接复用。如果你手头是两张单域名证书,就需要拆分成两个 server 块分别配置证书,并且通过 location 或 rewrite 做一层跳转,这个后边会细讲。

3. 匹配优先级:最容易踩坑的环节

如果说 server_name 是 Nginx 虚拟主机的核心,那匹配优先级就是这条“配置指令-server_name”最深的灵魂。很多诡异的问题,归根到底就是优先级理解不透彻。

3.1 精确匹配、通配符、正则的裁决顺序

Nginx 在处理 server_name 匹配时,遵循一套非常明确的优先级规则。先决顺序是:

  1. 完全精确匹配的 server_name。比如请求www.example.com,配置里有一条server_name www.example.com;,那它无条件胜出。
  2. 以*开头的最长通配符匹配。比如*.example.com和*.sub.example.com,如果请求是a.b.sub.example.com,且写了*.example.com与*.sub.example.com两条,则更长(更具体)的那条获胜。
  3. 以*结尾的最长通配符匹配。例如www.example.*这类写法,规则与上一条对称。
  4. 第一条正则表达式匹配,按配置文件里出现的先后顺序来决定。也就是说,写在前面正则优先,后面的正则即使表达式更具体,如果写在后面,也会输给前面的不够具体的正则。

这个顺序极其重要。很多人以为“后写的会覆盖先写的”,Nginx 的 location 匹配确实是前缀+正则的复杂规则,但 server_name 的优先级是另一套完全独立的规则,它压根不存在覆盖关系,而是按上述顺序层层裁决的。

3.2 用三个实例彻底搞懂优先级

为了让你彻底理解这套规则,我用实际场景演示一遍。

假设配置里存在以下几个 server 块:

server { listen 80; server_name example.com; # 精确命中 example.com } server { listen 80; server_name *.example.com; # 通配符匹配子域名 } server { listen 80; server_name ~^www\.example\.com$; # 正则匹配 www }

当请求example.com进来时,精确匹配规则生效,走第一个块。请求blog.example.com进来时,精确规则匹配不上(精确是要完全一致),于是按顺序轮到通配符,走第二个块。请求www.example.com进来时,精确匹配会先看有没有server_name www.example.com完全相同的条目,结果没有;其次看通配符*.example.com,它可以匹配,优先级排第二,但不要急——虽然它排在正则前,但这是按类别的优先级,在通配符内部再按最长匹配裁决,也就是说在正则这一层启动之前,通配符已经可能获胜了。

精确句子缺失时,*.example.com可以直接命中www.example.com,根本不会走到正则那一步。所以正则~^www\.example\.com$虽然写得花哨,但在这个例子里它对www.example.com起不到抢先作用。这个“通配符先于正则”的决策顺序,和部分开发者熟悉的 Apache 的 ServerAlias 匹配习惯很不一样,Nginx 下如果混用 Apache 的思路,就会出现“写了正则却不生效”的疑问。

3.3 真正需要正则的场景

正则不是没用,它主要用于处理通配符覆盖不了的规则。比如你要把2209-abc.example.com、2210-abc.example.com这类带编号的子域名统一到一个站点:

server { listen 80; server_name ~^\d{4}-abc\.example\.com$; }

这个场景用通配符是表达不了的,因为普通通配符只能匹配一个层级的完整段,不能匹配数字加连字符这种模式。再比如你想把hello.example.com.cn和hello.example.net.cn都合并进同一块配置,那就可以用:

server { listen 80; server_name ~^hello\.example\.(com|net)\.cn$; }

用正则时,我有一条铁律:写好之后先在本地把候选域名丢进去验证一遍。我常用nginx -T配合curl -v在测试环境模拟请求,看返回的 Server 头或者响应内容是不是预期站点,避免引入不可控匹配。

提示:正则里的点号务必写成\.。你写一个裸的.,在正则里是“匹配任意字符”的意思,等于放大了匹配范围,会造成虚拟主机互相串位的严重事故。

4. 实际场景中的 server_name 配置方案

原理讲清楚了,接下来直接进入可落地的方案。我在日常项目里最常用到的场景有三个:多站点 + HTTPS 证书联动、反向代理场景下 host 传递、以及泛域名解析下的安全隔离。

4.1 多站点 + HTTPS:server_name 与证书配置的联动

单域名证书时代,每个域名通常需要独立的证书文件,因此常见的做法是把每个域名拆成独立的 server 块,借助 80 端口跳转 443:

server { listen 80; server_name www.example.com example.com; return 301 https://www.example.com$request_uri; } server { listen 443 ssl; server_name www.example.com; ssl_certificate /etc/nginx/certs/www.example.com.crt; ssl_certificate_key /etc/nginx/certs/www.example.com.key; root /var/www/example; }

这里有一个很关键的细节:第一个 server 块作为 HTTP 入口,把所有 HTTP 请求统一 301 到 HTTPS。第二个 server 块真正承载业务。注意server_name www.example.com;只写了 www 域,如果你同时要处理裸域example.com的 HTTPS 访问,就得再来一个 443 server 块指向同一套代码目录。否则用户用裸域打开时,Host 是example.com,Nginx 匹配不到 443 块上的 www 项,就会落到默认 443 块,可能直接证书报错。

如果你用的是泛域名证书(比如*.example.com),可以把多个子域名写进同一个 server 块:

server { listen 443 ssl; server_name www.example.com api.example.com; ssl_certificate /etc/nginx/certs/example.com.pem; ssl_certificate_key /etc/nginx/certs/example.com.key; root /var/www/example; }

这种做法的最大好处是减少 server 块数量,也让证书的管理变得集中。但要注意,泛域名证书通常不覆盖裸域example.com,所以裸域最好单独配置一个 server 块,用专门证书或者做跳转。

4.2 反向代理场景:不要轻易修改 Host 头

在生产环境里,用 Nginx 做反向代理是再常见不过的事。比如你有一个后端服务跑在http://127.0.0.1:8080,前端通过api.example.com访问。这时候 server_name 不仅决定哪个 server 块处理请求,还会影响转发过程中的 Host 头语义。

一个基础的反向代理配置:

server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/nginx/certs/api.example.com.pem; ssl_certificate_key /etc/nginx/certs/api.example.com.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

其中proxy_set_header Host $host;是默认行为——$host 变量的值就是请求的 Host 头,当 Host 头与该 server 块的 server_name 匹配后,这个值会原样传给后端。这样后端能正确判断用户访问的域名,做路由判断、生成绝对链接、CORS 判断时都不会出错。

但有时候,你确实需要改写 Host 头。比如后端服务是写死的域名白名单,或者要实现某些固定回调逻辑,就有下面这种配置:

server { listen 80; server_name legacy.example.com; location / { proxy_pass http://10.0.0.5:8080; proxy_set_header Host legacy-backend.internal; } }

这种场景下,一定要想清楚:改动 Host 头后,后端的日志记录里看到的将是伪装的 Host,不便于排查问题。我的建议是,能不改就不要改;真要改,请在 header 里额外加一个X-Original-Host $host;保留原始值,方便定位。

4.3 泛域名解析下的安全隔离

某些 SaaS 系统会提供泛域名绑定能力,用户把自己的二级域名companyA.example.com指向服务器,系统要自动识别访问者的企业身份。这种需求用通配符 + 正则捕获非常合适:

server { listen 443 ssl; server_name ~^(?<company>.+)\.example\.com$; ssl_certificate /etc/nginx/certs/example.com.pem; ssl_certificate_key /etc/nginx/certs/example.com.key; location / { proxy_pass http://company-backend-$company; proxy_set_header X-Tenant-Id $company; } }

你甚至可以在proxy_pass里直接引用自动捕获的变量$company,实现企业代码的自动路由。需要注意的是,Nginx 在 server_name 正则捕获组用于后续变量引用时,有一定的版本兼容要求,较老版本可能不支持这种写法,建议在部署时用nginx -V检查版本,并做完整的回归测试。

这种泛域名绑定很有用,但安全隔离必须做扎实,否则一个企业的登录 Cookie 被另一个企业域名读取到,就是严重的数据泄露事故。核心思路是:所有企业域名都要生成独立的 Cookie 作用域,后端从X-Tenant-Id识别企业身份,同时要校验 Host 头是否匹配该企业域名的配置,防止纵向越权。

5. 常见问题与排查技巧实录

到了这一节,才是真正能帮你省时间的部分。我把过去几年遇到的高频问题整理成了实战排查手册,每个问题都给到可复现的排查路径。

5.1 请求全部落在了默认 server 块

症状表现是:无论输入哪个域名,打开的永远是同一个站点,配置文件里的多个 server_name 仿佛完全没有生效。

排查步骤:

  1. 执行nginx -T查看实际生效配置,这一步能过滤掉无效配置和语法错误。
  2. 确认监听端口有没有重复声明。比如 80 端口在两个 server 块里都写了listen 80;,且都没有声明default_server,那么配置文件里靠前的那个会成为默认站点。如果请求的 Host 值跟任何 server_name 都没匹配上,自然就落到默认站点。
  3. 确认请求确实发到了这台服务器。有时候 DNS 解析到了 CDN 或前置负载均衡,Nginx 收到的连接是从内网转发过来的,转发层可能修改了 Host 头。
  4. 确认 server_name 书写的边界。比如你填的是server_name example.com;,而用户是用www.example.com访问的,两个名的字符串并不相同,这时候请求就会落到默认 server 块里去。

这类问题九成以上都是第 2 步和第 4 步造成的,建议优先核查这两个方向。

5.2 通配符意外串到其它业务

症状表现是:很多无关的域名全都能命中同一个站点,或者一个子域名的页面错误地在另一个子域名的页面下展示。

遇到这种情况,先检查所有 server_name 是否混用了裸域与通配符。比如你有两个块,一个写着server_name example.com;,另一个写着server_name *.example.com;,请求blog.example.com时通配符会命中第二块,这符合预期。但如果第一个块写的是server_name example.com www.example.com;,这两个名称在 Nginx 眼里是同一优先级——都是精确匹配——分别独立命中,不会互相干扰。

真正容易串的是正则未转义。比如你本意匹配api.example.com,写的却是server_name ~^api.example.com$;,这里的点号就踩了我在前边强调的坑,它能匹配apixexamplexcom这类奇怪 Host。建议用nginx -T输出,挨个检查含正则的 server_name,把\.转义符补全。

5.3 HTTPS 证书反复告警“证书不匹配”

这个问题的根源往往不在证书本身,而在 server_name 对默认块的安排上。当客户端对某个域名发起 HTTPS 请求时,Nginx 会先根据 SNI 挑选证书。如果这个域名匹配不到任何具体的 443 server 块,就会使用默认 443 块的证书,于是形成证书与实际访问域名不一致的告警。

解决思路:

  1. 为所有对外域名建立对应的 443 server 块,并把证书配置到对应块上。
  2. 为兜底用的默认 443 块配一张覆盖面足够广的证书,或者放一张自签名证书,但要明白这只是兜底策略。
  3. 如果站点很多,建议使用 ACME 工具做证书签发与自动续签,并把证书路径通过变量引用,避免证书路径写死在每个 server 块里。

我在生产环境里见过一种急诊式做法:所有 443 server 块共用一个兜底证书,虽然浏览器会告警,但业务用了代理或专用客户端访问,感受不明显。这种方案只适合内网环境,公网站点我强烈不建议,因为现代浏览器的证书告警会直接劝退大部分普通用户。

5.4 reload 之后仍然不生效

改了配置文件nginx -s reload之后,新 server_name 完全没有生效,看起来像配置没被加载。

最常被忽略的原因是:Nginx 工作进程 fork 后,旧连接还由旧配置选定的 worker 维持。对于长连接(比如 WebSocket 或 HTTP/2 多路复用),客户端可能还在用旧的连接继续发请求,旧连接复用的是 reload 之前解析好的 server 块,所以新域名访问不到并不代表配置没加载。

如果用短连接也复现不了,那就可能是 DNS 缓存造成的。浏览器、操作系统、CDN、本地 hosts 解析链路上的任何一级缓存没有刷新,都会让域名继续指向旧地址或旧配置。验证方法很简单:在你本机的 hosts 文件里手动绑定域名到服务器 IP,然后curl -v --resolve强制指定新 IP 访问一次,即可区分是 Nginx 配置还是 DNS 缓存的问题。

5.5 default_server 与 IP 直访的防护

最后谈安全。公网服务器一天到晚会收到大量扫描请求,这些请求通常不带已知域名,Host 头千奇百怪。如果默认 server 块配置不谨慎,扫描器能拿到站点首页内容、指纹信息,甚至探测出后台登录地址。

我推荐基础防护配置:

server { listen 80 default_server; listen 443 ssl default_server; ssl_certificate /etc/nginx/certs/default.pem; ssl_certificate_key /etc/nginx/certs/default.key; server_name _; return 444; }

return 444;是 Nginx 特有的“关闭连接不返回响应”的指令,对未知域名的访问直接掐断连接,扫描器连 HTTP 状态都收不到,能有效降低打扰。这个默认块要配置在最容易访问到的位置,因为它对 80 和 443 端口的未知请求都生效。生产环境的服务器上,我是强烈建议加这一层防护的,别把没匹配到的请求放任进业务站点。

6. 最后的几条实操心得

再分享几个在长期实践中总结出的习惯,算不上什么高深技巧,但真的能让你少踩坑。

第一,用变量统一证书路径。别把证书绝对路径在每个 server 块里写成死字符串,配合include文件和 ACME 脚本,统一声明证书路径,到期前更换证书时才不会漏改某个站点。

第二,配置模板化。同一个业务站点的 server 块,用相同的骨架复制,只改动 server_name、root、proxy_pass 三个变量。模板化之后,从博客里的示例配置改成生产配置的时间以分钟计,也能大幅减少手滑写错的概率。

第三,写完配置第一件事是nginx -t。这不是废话,我见过有人写完一大段配置直接 reload 的,结果语法错误导致 Nginx 拒绝启动,线上业务瞬间中断。nginx -t只测试不生效,能在不影响线上服务的前提下提前发现问题。

第四,日志是一切排查的照妖镜。把access_log和error_log打开,遇到任何 server_name 匹配问题,先看请求日志中记录的实际 Host 值、请求路径、状态码。比猜和试快得多。

这条“配置指令-server_name”虽然只是 Nginx 配置文件里不起眼的一行,但它决定了虚拟主机能否正确识别、证书能否正确下发、反代能否正确寻址。把它的匹配优先级、通配符写法、正则细节和默认站点策略吃透,再复杂的多站点架构,你也能做到心中有数。

写这份内容的时候,我又回想了一遍那次蹩脚的正则事故——排查到最后,发现是自己写的点号没有转义,那几行正则让一堆意外域名全吸附到了同一个站点上。当时懊恼了很久,但也因为那次踩坑,后来每次动 server_name 都变得格外谨慎:改完必先nginx -t,然后 curl 带 Host 访问一遍测试域名,最后才敢 reload。希望你不用经历我那次一样的尴尬,直接把这份经验带回去用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询