关键词:nginx / 配置心智模型 / http-server-location / 路由优先级 / location / rewrite / 反向代理
引言:从「改一行配置」到「线上整站 502」
有一次,一个同事说要「顺手改下 nginx 配置」,给某个接口加一条简单的转发规则。
几分钟后,测试环境一片 502,登录页面也打不开了,大家第一反应是:是不是后端挂了、是不是 Redis 崩了。
最后排查下来,问题既不是网络,也不是代码,而是:
- 在
server里多加了一条location; - 少看了几行已有规则;
- 让一个优先级更高的
location把全站请求都「截胡」了。
nginx 配置最大的坑,不是你不会写,而是你以为你写的是这条路,其实它走的是另一条。
这一篇,我们就不从「指令大字典」开始,而是从一份最常见的 server 配置入手,把 nginx 的基本心智模型和路由优先级讲清楚。
一、先把大图看清:http / server / location 到底在干嘛?
绝大部分人第一次看到 nginx 配置,大概是这样的结构:
nginx
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
root html;
index index.html index.htm;
}
location /test {
proxy_pass https://wangmiaozero.cn;
}
}
}
如果只记这一句就够用了:
http是「总配置」,server是「虚拟主机」,location是「这一台主机上的路由规则」。
1. http:全局规则与公共基础设施
http { ... } 里通常放的是「所有 HTTP 服务都能用到的东西」:
include mime.types:引入文件类型映射;default_type:默认响应类型;- 各种全局的缓存、日志、连接数、超时配置等。
你可以把 http 想象成「一个机房」:
- 机房里会有一堆机器(
server); - 机房级别会有统一的规矩(
http里的配置); - 单个机器也可以在自己房间里加点特殊设置(
server级别覆盖)。
2. server:一台逻辑上的「虚拟主机」
server { ... } 就是一台虚拟主机,它通过:
listen:监听端口;server_name:匹配域名;
来决定「这个请求是不是该我接」。
一个 nginx.conf 里可以有很多个 server:
server_name www.xxx.com:网站 A;server_name api.xxx.com:网站 B;server_name *.internal.xxx.com:内部管理系统等。
用户的 HTTP 请求,先是被某一个 server 接住,才会往 location 里面走。
3. location:这一台主机上的「路由与分发规则」
有了 server 之后,剩下的问题就是:
收到一个请求路径
/foo/bar,到底交给谁处理?
这就是 location 的工作了。
常见的指令大致长这样:
root:静态资源根目录;index:默认首页文件;proxy_pass:把请求转发到后端;rewrite:在当前域名下改写路径。
日常开发中,大部分「玄学问题」都出在 location 上 ------
要么没命中预期的规则,要么被更高优先级的规则截胡。
二、location 的 7 种写法,其实只是在玩「谁先说了算」
网上经常会看到这样的总结:
「= 精确匹配、^~ 前缀、~ 正则、/ 通吃」,记完一遍下次又忘了。
我们换个更工程一点的视角:location 玩的是「优先级 + 是否继续匹配」这两个维度。
1. 先看 7 种写法
按照官方语义 + 实战行为,location 粗略可以分成 7 类,从高到低是:
=精确匹配- 完整路径匹配(如
location /test/aaa/bbb.html) ^~ /test:以/test开头,命中后不再往后找~ pattern:区分大小写的正则,命中后不再往后找~* pattern:不区分大小写的正则,命中后不再往后找/test:普通前缀匹配,命中后还会继续往后找更长的前缀/:兜底规则,什么都匹配,优先级最低
你可以记成两个简单的问题:
- 当前规则优先级高不高?
- 命中之后还要不要继续往后找?
2. 两条大原则 + 两条细则
如果你只想背少量规则,把这 4 条刻进脑子就够了:
-
整体优先级:
精确匹配(=)>完整路径>^~> 正则(~ / ~*) > 普通前缀(/xxx) >/ -
是否继续匹配:
除了普通前缀
/xxx会继续往后找,其它类型(= / ^~ / ~ / ~*)一旦命中就停止。 -
同类型之间:
- 多个正则之间 / 多个
^~之间:命中第一个就停,顺序很重要; - 多个普通前缀(都是
/xxx开头):最长匹配优先,跟先后顺序无关。
- 多个正则之间 / 多个
-
/永远是兜底:不管前面写了多少规则,
/都是最后一条救命稻草。
如果觉得记不住,可以把 location 理解成:
「先挑最有资格说话的人,然后看他有没有把话说死」。
三、通过 5 个小实验,把优先级打进肌肉记忆
理解规则是一回事,能不能在脑子里「算出结果」是另一回事。
下面的例子全部可以直接丢进 nginx 里测试。
示例一:精确匹配永远优先于普通 /path
nginx
location /hello.json {
default_type text/html;
return 200 '111';
}
location = /hello.json {
default_type text/html;
return 200 '222';
}
访问 /hello.json,返回的是 222。
- 原因:
=精确匹配优先级最高,先命中就直接停; - 普通
/hello.json虽然看起来一样,但在规则里属于「前缀匹配」。
示例二:^~ 比正则更「强势」
nginx
# 虽然 222 在后面,但是由于 ^~ 优先级更高,所以第二个生效
location ~ /test/aaa {
return 200 '111';
}
location ^~ /test/aaa {
return 200 '222';
}
访问 /test/aaa/bbb.json,返回的是 222。
解释:
^~ /test/aaa属于「前缀但不再继续找」这一类,优先级高于正则;- 即便它在后面出现,依然会先拿到话语权。
示例三:多个正则,先写的先赢
nginx
# 多个正则之间,第一个匹配生效,与顺序有关
# 虽然越往后匹配越精确,但只要是正则,匹配到第一个就停止
location ~ / {
return 200 '111';
}
location ~ /test/aaa {
return 200 '222';
}
location ~ /test/aaa/.*\.(gif|jpg|jpeg)$ {
return 200 '333';
}
访问 /test/aaa/bbb.json,命中的其实是第一条正则(返回 111),因为:
/这个正则也能匹配一切路径;- 正则之间遵循「先写先赢」,匹配到第一个就不再往后看。
示例四:同路径前缀下,正则 > 普通前缀
nginx
# 不管二者如何交换顺序,始终都是第二个生效
location /test/aaa {
return 200 '111';
}
location ~ /test/aaa {
return 200 '222';
}
访问 /test/aaa/bbb.json,返回 222:
~ /test/aaa是正则,优先级比普通前缀/test/aaa更高;- 先比「种类优先级」,再谈顺序。
示例五:都是前缀匹配时,看谁更长
nginx
# 都是 / 开头时,最长匹配生效,和先后顺序无关
location /test/ {
return 200 '111';
}
location /test/aaa/ccc {
return 200 '222';
}
location /test/aaa/ {
return 200 '333';
}
location / {
return 200 '444';
}
访问 /test/aaa/bbb/ccc.json:
/test/能匹配;/test/aaa/也能匹配;/test/aaa/ccc也能匹配;
最终选的是字符串最长 的那条:/test/aaa/ccc → 返回 222。
四、rewrite 和 proxy_pass:重写 vs 转发,不要搞混
日常配置里,大家最容易把 rewrite 和 proxy_pass 混成一团:
「反正都是改 URL,对吧?」------其实差别挺大。
1. rewrite:只在当前域名里改路由
rewrite 的典型用途是:
- 在同一个域名 下,把
/old/path改写成/new/path; - 决定是内部跳转(
last)还是返回 301/302(redirect/permanent)。
它做的只是路径层面的改写,不会帮你换服务器、换端口:
nginx
location /old {
rewrite ^/old/(.*)$ /new/$1 last;
}
2. proxy_pass:把请求「交给别人处理」
proxy_pass 则是反向代理:
- 可以把请求丢给任意地址:
http://127.0.0.1:8080、https://api.xxx.com; - 可以在这一层加各种 header、超时、缓冲配置等。
nginx
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_connect_timeout 10;
proxy_pass http://127.0.0.1:6666;
}
一句话区分:
rewrite:路由改写,只在当前主机内「换一条路走」;proxy_pass:反向代理,把整条请求「交给另一台服务」。
五、nginx 配置的「可预期性」:从记语法到记行为
很多人觉得 nginx 配置玄学,本质上是因为:
- 只记住了「怎么写」,没记住「会发生什么」;
- 只在单条规则上做实验,很少在多个
location组合下做验证。
如果你把这一篇的核心知识点压缩成一张纸,大概就是:
http / server / location的分层职责;- 7 种
location写法的优先级 + 是否继续匹配; - 5 个例子里「谁先说话、谁最后拍板」的行为模式;
rewrite只改路径,proxy_pass才是真正转发。
nginx 最容易犯错的地方,不在于你不会写指令,
而在于你没能在脑子里把「真正的匹配过程」想一遍。
下一篇(中篇),我们会把视角从「路由规则」拉到「流量入口」:
从 HTTPS、反向代理、跨域、永久跳转,到泛二级域名这些实际工程场景,
把 nginx 当成一条真正的「流量总入口」来看,而不仅仅是一个静态资源服务器。