02-配置体系与请求匹配
02-配置体系与请求匹配
1. 阶段定位
第二阶段是 Nginx 从“会部署、会启动、会看日志”进入“能写正确、稳定、可维护配置”的关键阶段。第一阶段重点是让 Nginx 跑起来,第二阶段重点是回答下面几个问题:
1 | 一个请求进来以后: |
很多 Nginx 线上问题并不是因为 Nginx 不稳定,而是因为配置匹配规则理解不清。例如:
- 请求进入了错误的
server。 location优先级判断错误。root和alias混用导致静态资源 404。try_files内部跳转不清楚,导致前端路由异常。rewrite last和break混用,导致 URI 改写结果不符合预期。$uri和$request_uri混淆,导致日志、跳转、代理路径判断错误。
本阶段完成后,你应该能做到:
- 能读懂并设计 Nginx 配置层级结构。
- 能解释指令在哪个上下文生效。
- 能判断配置继承和覆盖关系。
- 能准确判断请求命中哪个
server。 - 能准确判断请求命中哪个
location。 - 能解释
root、alias、index、try_files的差异。 - 能正确使用
return做跳转和固定响应。 - 能谨慎使用
rewrite,避免滥用和误匹配。 - 能使用自定义 access log 打印变量,验证请求匹配过程。
- 能写出结构清晰、可维护、可排查的 Nginx 配置。
2. 学习边界
第二阶段聚焦配置体系和请求匹配,不深入反向代理、负载均衡、HTTPS、安全限流和性能调优。
2.1 本阶段重点
- 配置上下文:
main、events、http、server、location、upstream。 - 指令作用域、继承和覆盖。
listen与默认 server。server_name匹配规则。location匹配规则和优先级。- URI、request URI、normalized URI 的区别。
root与alias的路径拼接差异。index的目录首页行为。try_files的文件检测和内部跳转。return的固定响应与跳转。rewrite的 URI 改写、正则捕获、last、break。- 常用变量:
$uri、$request_uri、$args、$host、$http_host、$remote_addr。 - 自定义日志格式验证匹配过程。
2.2 本阶段暂不深入
下面内容后续阶段再系统学习:
proxy_pass的 URI 拼接规则。upstream负载均衡算法。- 后端超时、重试、连接池。
- HTTPS、证书、HTTP/2。
- gzip、缓存、sendfile 深度优化。
- 限流、限并发、安全防护。
- 灰度发布、高可用、热升级。
- Nginx 源码 phase handler 机制。
3. 配置体系总览
Nginx 配置不是简单的键值对文本,而是一套有上下文、作用域、继承关系和执行阶段的配置体系。
典型结构:
1 | user nginx; |
可以把 Nginx 配置理解成下面层级:
1 | main 全局配置 |
3.1 main 上下文
main 是配置文件最外层,不需要显式写出名称。
适合放:
- 运行用户。
- worker 进程数。
- pid 文件。
- 全局错误日志。
- 全局 include。
- 全局资源限制类配置。
示例:
1 | user nginx; |
3.2 events 上下文
events 用于配置事件模型和连接处理能力。
1 | events { |
这一阶段只需要知道:它不处理 HTTP 路由,不写 server、location、root。
3.3 http 上下文
http 是 HTTP 配置的总入口。
适合放:
- MIME 类型。
- 默认响应类型。
- 日志格式。
- 全局访问日志。
- gzip、sendfile 等 HTTP 级配置。
- 多个
server。 upstream。map等 HTTP 级逻辑。
示例:
1 | http { |
3.4 server 上下文
server 表示一个虚拟主机,也可以理解为一个站点入口。
1 | server { |
server 主要解决:
1 | 这个请求应该进入哪个站点? |
判断依据主要是:
- 监听端口。
- 监听 IP。
- Host 头。
server_name。- 默认 server。
3.5 location 上下文
location 用于匹配 URI 路径,决定请求在当前 server 内如何处理。
1 | location /static/ { |
location 主要解决:
1 | 这个请求在当前站点里应该由哪段规则处理? |
3.6 upstream 上下文
upstream 用于定义后端服务组,第三阶段反向代理与负载均衡会重点学习。
1 | upstream backend_api { |
本阶段只需要知道:upstream 通常写在 http 中,不能写在 server 或 location 内。
4. 指令上下文与作用域
不同指令只能出现在特定上下文中。写错位置,nginx -t 会报错。
4.1 常见上下文表
| 指令 | 常见合法上下文 | 说明 |
|---|---|---|
user |
main | worker 运行用户 |
worker_processes |
main | worker 进程数量 |
events |
main | 事件配置块 |
http |
main | HTTP 配置块 |
server |
http | 虚拟主机 |
location |
server、location | URI 匹配配置 |
upstream |
http | 后端服务组 |
log_format |
http | 定义日志格式 |
access_log |
http、server、location | 访问日志 |
error_log |
main、http、server、location | 错误日志 |
root |
http、server、location | 静态资源根目录 |
index |
http、server、location | 默认首页 |
try_files |
server、location | 文件探测和内部跳转 |
return |
server、location、if | 返回状态码或重定向 |
rewrite |
server、location、if | URI 改写 |
4.2 常见上下文错误
错误示例:把 server 写在最外层。
1 | server { |
正确示例:server 应该在 http 内。
1 | http { |
错误示例:把 log_format 写在 server 内。
1 | server { |
log_format 应写在 http 上下文。
1 | http { |
5. 指令继承与覆盖
Nginx 配置存在继承和覆盖关系。很多指令可以在 http、server、location 多层配置,子级不配置时使用上级配置,子级配置后覆盖上级。
5.1 access_log 继承示例
1 | http { |
这个例子中,server 和 location 没有单独配置 access_log,所以继承 http 级别访问日志。
5.2 server 覆盖 http
1 | http { |
这个例子中,当前 server 的访问日志会写入 /var/log/nginx/example.access.log,而不是全局 access.log。
5.3 location 覆盖 server
1 | server { |
访问 /download/file.zip 时,会使用 location 内的日志路径。
5.4 继承不是简单叠加
很多指令不是“追加”,而是“覆盖”。例如子级配置了新的 access_log 后,通常不会同时自动写上级日志,除非你显式配置多个 access_log。
学习阶段建议:
- 全局默认配置放在
http。 - 站点独有配置放在
server。 - 特殊路径配置放在
location。 - 不要在多个层级重复写同一指令,除非明确需要覆盖。
6. 请求匹配总流程
理解 Nginx 配置匹配,必须先建立整体流程。
1 | 客户端请求 |
6.1 示例请求
1 | GET /static/app.js?version=1 HTTP/1.1 |
Nginx 会依次判断:
- 请求打到哪个本地 IP 和端口。
- 该端口下有哪些
server。 - Host
www.example.com匹配哪个server_name。 - URI
/static/app.js匹配哪个location。 - 根据
root、alias、try_files等规则处理。
6.2 两级匹配核心
1 | server 匹配:解决“进哪个站点” |
不要把 server_name 和 location 混为一谈。server_name 看 Host,location 看 URI。
7. listen 与默认 server
listen 决定 Nginx 在哪个 IP 和端口上接收请求。多个 server 可以监听同一个端口,此时 Nginx 会结合 server_name 和默认 server 决定请求进入哪个 server。
7.1 基础 listen 写法
1 | server { |
表示监听所有可用 IP 的 80 端口。
常见写法:
| 写法 | 含义 |
|---|---|
listen 80; |
监听 80 端口,通常绑定所有地址 |
listen 0.0.0.0:80; |
监听所有 IPv4 地址的 80 端口 |
listen 127.0.0.1:80; |
只监听本机回环地址 |
listen 8080; |
监听 8080 端口 |
listen [::]:80; |
监听 IPv6 80 端口 |
listen 80 default_server; |
当前 server 作为该监听地址上的默认 server |
7.2 同端口多 server
1 | server { |
测试:
1 | curl -H 'Host: a.example.com' http://127.0.0.1/ |
预期:
1 | server a |
7.3 default_server 的作用
当请求 Host 没有匹配任何 server_name 时,会进入当前监听地址的默认 server。
1 | server { |
这个配置中:
- Host 是
www.example.com,进入第二个 server。 - Host 是
unknown.example.com,进入第一个 default server。 - 没有 Host 或 Host 异常时,也可能进入 default server。
return 444 是 Nginx 特有状态,表示直接关闭连接,不返回响应。生产中常用它处理未知域名访问。
7.4 没写 default_server 时怎么办
如果同一个 listen 地址上没有显式声明 default_server,Nginx 通常会把配置中第一个出现的 server 作为默认 server。
不推荐依赖这个隐式规则。建议显式配置:
1 | server { |
这样配置更清晰,后续新增站点时也不容易误伤。
7.5 IP 维度的默认 server
默认 server 是按监听地址和端口区分的。
1 | server { |
如果一台服务器有多个 IP,不同 IP:port 可以有不同默认 server。
7.6 listen 排查命令
查看配置最终展开:
1 | sudo nginx -T | grep -n "listen\|server_name" |
查看端口监听:
1 | sudo ss -lntp | grep nginx |
测试指定 Host:
1 | curl -v -H 'Host: www.example.com' http://127.0.0.1/ |
8. server_name 匹配规则
server_name 用于根据 HTTP Host 头选择虚拟主机。
8.1 基础写法
1 | server { |
一个 server 可以配置多个名称:
1 | server { |
8.2 server_name 匹配优先级
Nginx 的 server_name 匹配大致优先级如下:
1 | 1. 精确名称匹配 |
8.3 精确匹配
1 | server { |
测试:
1 | curl -H 'Host: www.example.com' http://127.0.0.1/ |
精确匹配优先级最高。
8.4 前缀通配符匹配
1 | server { |
可匹配:
1 | www.example.com |
通常不匹配裸域名:
1 | example.com |
如果裸域名也要匹配,应该显式写出:
1 | server_name example.com *.example.com; |
8.5 后缀通配符匹配
1 | server { |
可匹配:
1 | mail.example.com |
这种写法相对少用,可维护性一般,不建议滥用。
8.6 正则匹配
正则 server_name 使用 ~ 开头。
1 | server { |
可匹配:
1 | www1.example.com |
正则匹配注意事项:
- 正则匹配优先级低于精确匹配和通配符匹配。
- 多个正则 server 按配置出现顺序匹配,第一个命中生效。
- 正则过多会降低可读性和可维护性。
- 生产环境应优先使用精确名称和清晰通配符。
8.7 server_name _ 的意义
常见配置:
1 | server { |
_ 并不是特殊通配符,只是一个普通但通常不会被真实 Host 使用的名称。真正让它成为兜底 server 的是 default_server,不是 _ 本身。
8.8 Host、$host 与 $http_host
Nginx 中常用两个变量观察 Host:
| 变量 | 说明 |
|---|---|
$http_host |
原始 Host 请求头,通常保留端口 |
$host |
规范化后的 Host,可能来自请求行、Host 头或 server_name,通常不带端口 |
示例请求:
1 | curl -H 'Host: www.example.com:8080' http://127.0.0.1/ |
可能看到:
1 | $http_host = www.example.com:8080 |
调试 Host 匹配时,建议自定义日志同时打印这两个变量。
9. URI 与请求变量基础
第二阶段最容易混淆的是 URI 相关变量。理解它们对 location、try_files、rewrite、日志排查都很重要。
9.1 URL、URI、请求参数
请求示例:
1 | http://www.example.com/static/app.js?version=1&debug=true |
拆开看:
| 部分 | 内容 |
|---|---|
| scheme | http |
| host | www.example.com |
| path / URI 路径 | /static/app.js |
| query string | version=1&debug=true |
| request URI | /static/app.js?version=1&debug=true |
9.2 $request_uri
$request_uri 表示客户端发来的原始 URI,通常包含查询参数。
1 | /static/app.js?version=1&debug=true |
特点:
- 保留原始请求中的参数。
- 通常不会随着内部 URI 规范化而变化。
- 很适合日志记录和跳转保留原始路径。
9.3 $uri
$uri 表示 Nginx 处理后的规范化 URI,不包含查询参数。
1 | /static/app.js |
特点:
- 不包含 query string。
- 可能被
rewrite、try_files内部跳转改变。 - 常用于路径匹配、文件查找和内部处理。
9.4 $args 与 $query_string
$args 表示查询参数部分:
1 | version=1&debug=true |
$query_string 通常等价于 $args。
可以通过下面方式返回变量观察:
1 | location /debug-uri { |
测试:
1 | curl -H 'Host: www.example.com:8080' 'http://127.0.0.1/debug-uri?a=1&b=2' |
9.5 URI 变量对比表
| 变量 | 是否包含参数 | 是否可能被内部改写影响 | 典型用途 |
|---|---|---|---|
$request_uri |
是 | 通常保留原始请求 | 日志、跳转、保留原始路径 |
$uri |
否 | 会 | 文件查找、内部跳转、匹配调试 |
$args |
只包含参数 | 可被修改 | 参数透传、日志 |
$is_args |
有参数时为 ? |
跟随 args | 拼接跳转 URL |
$host |
不包含路径 | Host 规范化结果 | 域名判断、代理头 |
$http_host |
原始 Host 头 | 原样来自请求头 | 调试 Host |
10. location 匹配规则
location 是第二阶段最重要的知识点之一。它决定 URI 在选定 server 内由哪段配置处理。
10.1 location 常见类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 精确匹配 | location = / |
URI 必须完全相等 |
| 前缀匹配 | location /static/ |
URI 以指定前缀开头 |
| 优先前缀匹配 | location ^~ /static/ |
命中后不再检查正则 |
| 区分大小写正则 | location ~ \.jpg$ |
正则匹配,区分大小写 |
| 不区分大小写正则 | location ~* \.jpg$ |
正则匹配,不区分大小写 |
| 通用前缀 | location / |
兜底路径匹配 |
10.2 location 匹配优先级
核心规则可以记成:
1 | 1. 先检查精确匹配 location = |
10.3 示例配置
1 | server { |
测试结果:
| 请求 URI | 命中 location | 原因 |
|---|---|---|
/ |
location = / |
精确匹配优先级最高 |
/abc |
location / |
只有通用前缀匹配 |
/static/app.js |
location /static/ |
最长前缀,正则不命中 |
/static/logo.jpg |
location ~ \.jpg$ |
前缀先记录,随后正则命中并覆盖 |
/static/images/a.jpg |
location ^~ /static/images/ |
^~ 最长前缀命中后跳过正则 |
/static/a.PNG |
location ~* \.png$ |
不区分大小写正则命中 |
10.4 精确匹配 location =
1 | location = /favicon.ico { |
适合处理:
/首页。/favicon.ico。/robots.txt。- 固定健康检查路径,例如
/health。
精确匹配性能和可读性都很好,适合高频固定路径。
10.5 普通前缀匹配
1 | location /static/ { |
可匹配:
1 | /static/app.js |
多个普通前缀同时匹配时,使用最长的那个作为候选。
10.6 ^~ 优先前缀匹配
1 | location ^~ /assets/ { |
如果 URI 以 /assets/ 开头,且这是最长前缀匹配,Nginx 会直接使用该 location,不再检查正则 location。
适合场景:
- 静态资源目录明确,不希望被后面的正则劫持。
- 下载目录明确,需要稳定匹配。
- 图片、文件、前端资源路径不希望进入动态规则。
10.7 正则 location
1 | location ~ \.php$ { |
1 | location ~* \.(jpg|jpeg|png|gif|webp)$ { |
注意:
~区分大小写。~*不区分大小写。- 多个正则按配置顺序匹配,第一个命中生效。
- 正则 location 可读性较差,过多会增加维护成本。
10.8 location / 兜底
1 | location / { |
location / 几乎可以匹配所有 URI,常作为兜底规则。建议每个 server 都有清晰的兜底 location,避免请求落入不可预期行为。
10.9 location 排查方法
为了确认请求进入哪个 location,可以临时写 return:
1 | location /api/ { |
也可以配置调试日志格式:
1 | log_format match_debug '$remote_addr host=$host request="$request" uri=$uri request_uri=$request_uri status=$status'; |
然后用 curl 验证:
1 | curl -H 'Host: www.example.com' 'http://127.0.0.1/static/app.js?version=1' |
11. root 与 alias
root 和 alias 都用于配置静态资源路径,但它们的路径拼接规则完全不同,是 Nginx 新手最容易踩坑的地方之一。
11.1 root 的拼接规则
root 的规则是:
1 | 真实文件路径 = root 路径 + 完整 URI |
示例:
1 | location /static/ { |
请求:
1 | /static/app.js |
实际查找:
1 | /data/www/static/app.js |
也就是说,location 匹配到的 /static/ 仍然会保留在文件路径中。
11.2 alias 的拼接规则
alias 的规则是:
1 | 真实文件路径 = alias 路径 + 去掉 location 前缀后的剩余 URI |
示例:
1 | location /static/ { |
请求:
1 | /static/app.js |
实际查找:
1 | /data/assets/app.js |
这里 /static/ 不会出现在真实文件路径中。
11.3 root 与 alias 对比
| 配置 | 请求 URI | 实际文件路径 |
|---|---|---|
location /static/ { root /data/www; } |
/static/app.js |
/data/www/static/app.js |
location /static/ { alias /data/assets/; } |
/static/app.js |
/data/assets/app.js |
location /images/ { root /data/www; } |
/images/logo.png |
/data/www/images/logo.png |
location /images/ { alias /data/pictures/; } |
/images/logo.png |
/data/pictures/logo.png |
11.4 alias 末尾斜杠问题
使用 alias 时,强烈建议 location 和 alias 都保持末尾斜杠一致。
推荐:
1 | location /static/ { |
不推荐:
1 | location /static/ { |
不一致时容易产生难以直观看出的路径拼接问题。
11.5 alias 与正则 location
正则 location 中使用 alias 时通常需要捕获组。
1 | location ~ ^/download/(.*)$ { |
请求:
1 | /download/a/b/report.pdf |
实际文件:
1 | /data/files/a/b/report.pdf |
这种配置可读性较差,生产中应谨慎使用。能用清晰前缀匹配解决时,不要优先使用正则 alias。
11.6 root 与 alias 选择建议
优先使用 root 的场景:
- URI 路径和磁盘路径结构一致。
/static/对应磁盘/data/www/static/。- 配置简单、直观、团队容易理解。
适合使用 alias 的场景:
- URI 路径和磁盘路径结构不一致。
/static/需要映射到/data/assets/。- 某个 URL 前缀只是对外暴露路径,不希望出现在磁盘路径中。
建议:
1 | 能用 root 清晰表达,就优先 root。 |
12. index 指令
index 用于访问目录 URI 时,指定默认首页文件。
12.1 基础示例
1 | location / { |
请求:
1 | / |
Nginx 会尝试查找:
1 | /data/www/site/index.html |
12.2 访问目录与访问文件
如果请求:
1 | /about/ |
且配置:
1 | location / { |
Nginx 会尝试查找:
1 | /data/www/site/about/index.html |
如果请求:
1 | /about.html |
则会直接查找:
1 | /data/www/site/about.html |
12.3 没有 index 时的 403
如果访问的是目录,并且目录下没有 index 指定的文件,通常会返回 403 Forbidden,错误日志中可能出现:
1 | directory index of "/data/www/site/" is forbidden |
解决方式:
- 补充
index.html。 - 修改
index指令。 - 如果确实需要列目录,开启
autoindex on;。
学习阶段了解 autoindex 即可,不建议生产随意开启目录列表。
13. try_files 执行逻辑
try_files 用于按顺序检查文件或目录是否存在,命中则使用该路径,全部不命中则跳转到最后一个参数。
13.1 基础语法
1 | try_files file ... uri; |
常见写法:
1 | location / { |
执行逻辑:
1 | 1. 检查 /data/www/site + $uri 是否存在 |
13.2 静态站点常用配置
1 | server { |
适合普通静态站点:
- 文件存在则返回文件。
- 目录存在则尝试目录首页。
- 都不存在则返回 404。
13.3 前端 history 路由配置
Vue、React、Angular 等前端单页应用使用 history 路由时,刷新 /user/profile 可能需要回退到 /index.html。
1 | server { |
请求 /user/profile:
1 | 1. 查找 /data/www/frontend/user/profile |
浏览器仍然显示 /user/profile,但 Nginx 返回的是前端入口文件 index.html。
13.4 try_files 最后一个参数
最后一个参数可以是 URI:
1 | try_files $uri $uri/ /index.html; |
也可以是状态码:
1 | try_files $uri $uri/ =404; |
也可以跳转到命名 location:
1 | location / { |
命名 location 以 @ 开头,只能内部跳转,不能被客户端直接请求。
13.5 try_files 与 root 的关系
try_files 检查文件时,会结合当前上下文中的 root 或 alias。
1 | root /data/www/site; |
请求:
1 | /static/app.js |
检查:
1 | /data/www/site/static/app.js |
13.6 try_files 常见误区
误区一:以为 try_files 会改变浏览器地址。
实际:try_files 的 /index.html 是内部跳转,不会改变浏览器地址栏。
误区二:忘记配置 root。
1 | location / { |
如果当前上下文没有正确 root,文件检查路径可能不是你预期的路径。
误区三:前端项目把所有请求都回退到 index.html,导致真实 404 文件也返回 200。
例如 /not-exist.png 也返回 index.html,这对静态资源不友好。更稳妥的做法是静态资源单独配置:
1 | location ^~ /assets/ { |
14. return 指令
return 用于直接返回状态码、文本或重定向。它比 rewrite 更直接、更清晰,能用 return 解决的跳转,不要优先使用 rewrite。
14.1 返回固定状态码
1 | location /health { |
测试:
1 | curl -i http://127.0.0.1/health |
14.2 返回 404
1 | server { |
用于未知域名兜底。
14.3 返回 301 永久重定向
1 | server { |
含义:访问 example.com 时,永久跳转到 www.example.com,并保留原始路径和参数。
请求:
1 | http://example.com/a/b?x=1 |
跳转到:
1 | http://www.example.com/a/b?x=1 |
14.4 返回 302 临时重定向
1 | location /old-page { |
302 适合临时跳转,301 适合永久规范化跳转。
14.5 return 与 rewrite 的选择
优先选择 return 的场景:
- 域名跳转。
- HTTP 到 HTTPS 跳转。
- 固定旧路径跳转新路径。
- 健康检查返回文本。
- 默认 server 返回 404 或 444。
return 的优点:
- 执行直接。
- 可读性强。
- 不依赖复杂正则。
- 不容易产生内部跳转副作用。
15. rewrite 指令
rewrite 用于基于正则改写 URI。它很强大,但也容易被滥用。第二阶段要掌握它的基本行为,更要知道什么时候不该用。
15.1 基础语法
1 | rewrite regex replacement [flag]; |
示例:
1 | location / { |
请求:
1 | /old/a.html |
改写为:
1 | /new/a.html |
15.2 常见 flag
| flag | 说明 |
|---|---|
last |
停止当前 rewrite,使用新 URI 重新进行 location 匹配 |
break |
停止当前 rewrite,在当前 location 继续处理 |
redirect |
返回 302 临时重定向 |
permanent |
返回 301 永久重定向 |
15.3 last 示例
1 | server { |
请求:
1 | curl http://127.0.0.1:8088/old/a |
流程:
1 | 1. 先命中 location /old/ |
15.4 break 示例
1 | location /old/ { |
break 不会重新发起 location 匹配,而是在当前 location 内继续处理。
15.5 permanent 与 redirect
1 | rewrite ^/old-page$ /new-page permanent; |
等价于更推荐的:
1 | location = /old-page { |
如果只是跳转,优先使用 return。
15.6 rewrite 与参数
默认情况下,replacement 中不写 ? 时,原始参数可能会被保留。
1 | rewrite ^/old$ /new last; |
请求:
1 | /old?a=1 |
可能变成:
1 | /new?a=1 |
如果想丢弃原参数,可以在 replacement 末尾加 ?:
1 | rewrite ^/old$ /new? last; |
这类细节容易引发线上问题,因此跳转类需求建议优先用 return 显式拼接。
15.7 rewrite 常见误区
- 用 rewrite 做所有跳转,导致配置难读。
- 不理解
last会重新匹配 location。 - 不理解
break仍留在当前 location。 - 正则写得过宽,误伤其他 URI。
- 忘记参数是否保留。
- 多条 rewrite 连续执行,最终 URI 难以判断。
建议:
1 | 能用 return,就不用 rewrite。 |
16. Nginx 常用变量
变量是理解 Nginx 请求处理过程的重要工具。第二阶段不要求掌握所有变量,但必须熟悉常用于匹配、日志、跳转和排查的变量。
16.1 请求与 URI 变量
| 变量 | 示例 | 说明 |
|---|---|---|
$request |
GET /a?x=1 HTTP/1.1 |
完整请求行 |
$request_method |
GET |
请求方法 |
$request_uri |
/a?x=1 |
原始请求 URI,包含参数 |
$uri |
/a |
规范化 URI,不包含参数,可能被内部改写 |
$document_uri |
/a |
通常等同于 $uri |
$args |
x=1 |
查询字符串 |
$query_string |
x=1 |
通常等同于 $args |
$is_args |
? 或空 |
有参数时为 ?,无参数为空 |
常见拼接:
1 | return 301 https://www.example.com$uri$is_args$args; |
如果只是保留用户原始路径和参数,通常更简单:
1 | return 301 https://www.example.com$request_uri; |
16.2 Host 与客户端变量
| 变量 | 说明 |
|---|---|
$host |
规范化 Host,不一定完全等于原始请求头 |
$http_host |
原始 Host 请求头 |
$server_name |
当前匹配 server 的 server_name |
$server_addr |
服务端地址 |
$server_port |
服务端端口 |
$remote_addr |
客户端 IP |
$remote_port |
客户端端口 |
$server_protocol |
协议版本,例如 HTTP/1.1 |
注意:如果 Nginx 前面还有负载均衡或 CDN,$remote_addr 可能是上一层代理 IP,而不是真实用户 IP。真实客户端 IP 的处理后续阶段再学习。
16.3 请求头变量
Nginx 会把请求头转换成 $http_头名 变量:
1 | User-Agent -> $http_user_agent |
示例:
1 | log_format header_debug '$remote_addr host=$host ua="$http_user_agent" xff="$http_x_forwarded_for"'; |
16.4 响应与日志变量
| 变量 | 说明 |
|---|---|
$status |
HTTP 响应状态码 |
$body_bytes_sent |
响应体字节数 |
$bytes_sent |
总发送字节数 |
$request_time |
请求总耗时,单位秒 |
$time_local |
本地时间格式 |
$msec |
秒级时间戳,带毫秒 |
第二阶段重点用它们辅助判断匹配结果,不急着做性能分析。
16.5 变量调试 location
可以临时增加一个调试接口:
1 | location = /debug-vars { |
测试:
1 | curl -H 'Host: www.example.com:8088' 'http://127.0.0.1:8088/debug-vars?a=1&b=2' |
17. 自定义日志验证匹配过程
复杂配置不要靠猜,应该让日志帮助你确认请求进入了哪个 server、哪个 location,以及 URI 是否发生变化。
17.1 定义调试日志格式
在 http 中定义:
1 | log_format match_debug 'time=$time_local remote=$remote_addr ' |
在 server 中启用:
1 | access_log /var/log/nginx/match_debug.log match_debug; |
17.2 使用响应头标记 location
有时可以在不同 location 加响应头,辅助浏览器或 curl 判断命中规则。
1 | location /static/ { |
测试:
1 | curl -I http://127.0.0.1:8088/static/app.js |
观察:
1 | X-Matched-Location: /static/ |
17.3 使用 return 做最小验证
当你不确定匹配顺序时,最简单的验证方式就是临时 return。
1 | location ^~ /assets/ { |
测试:
1 | curl http://127.0.0.1:8088/assets/app.js |
如果返回 hit ^~ /assets/,说明 ^~ 阻止了正则继续匹配。
18. 多 server_name 实验
18.1 实验目标
验证同端口多个 server 时,Host 如何决定请求进入哪个虚拟主机。
18.2 实验配置
1 | server { |
18.3 验证命令
1 | sudo nginx -t |
18.4 预期结果
1 | server a |
18.5 实验结论
listen先确定端口范围。server_name再根据 Host 匹配 server。- 未匹配任何
server_name时进入default_server。 _本身不是通配符,兜底能力来自default_server。
19. location 优先级实验
19.1 实验目标
通过实际请求验证 location =、普通前缀、^~、~、~* 的优先级。
19.2 实验配置
1 | server { |
19.3 验证命令
1 | curl http://127.0.0.1:8089/ |
19.4 结论表
| 请求 | 预期返回 | 说明 |
|---|---|---|
/ |
exact root |
精确匹配最高 |
/abc |
prefix root |
兜底前缀 |
/static/app.js |
prefix static |
最长前缀且无正则命中 |
/static/logo.jpg |
regex jpg |
正则覆盖普通前缀 |
/static/images/a.jpg |
priority prefix images |
^~ 跳过正则 |
/static/a.PNG |
regex png ignore case |
~* 不区分大小写 |
20. root 与 alias 实验
20.1 实验目标
通过真实文件访问验证 root 和 alias 的路径拼接差异。
20.2 准备文件
1 | sudo mkdir -p /data/www/static |
20.3 实验配置
1 | server { |
20.4 关键现象
请求:
1 | curl http://127.0.0.1:8090/static-root/root.txt |
Nginx 查找:
1 | /data/www/static-root/root.txt |
这通常会 404,因为真实文件在:
1 | /data/www/static/root.txt |
如果要用 root,URI 前缀要和磁盘目录一致:
1 | location /static/ { |
请求:
1 | curl http://127.0.0.1:8090/static-alias/alias.txt |
Nginx 查找:
1 | /data/assets/alias.txt |
这就是 alias 的前缀替换效果。
21. try_files 实验
21.1 普通静态站点
1 | server { |
准备文件:
1 | sudo mkdir -p /data/www/site/about |
测试:
1 | curl http://127.0.0.1:8091/ |
21.2 前端 history 路由
1 | server { |
这个配置的好处:
/assets/app.js必须真实存在,不存在就 404。/user/profile不存在时回退到/index.html。- 前端 history 路由刷新不再由 Nginx 返回 404。
22. rewrite 与 return 实验
22.1 return 域名跳转
1 | server { |
测试:
1 | curl -I -H 'Host: old.example.com' 'http://127.0.0.1:8093/a/b?x=1' |
观察 Location:
1 | Location: http://new.example.com/a/b?x=1 |
22.2 rewrite last
1 | server { |
测试:
1 | curl 'http://127.0.0.1:8094/old/test?a=1' |
重点观察:
$uri可能变成/new/test。$request_uri仍能体现原始请求/old/test?a=1。last会重新匹配 location。
22.3 rewrite break
1 | server { |
测试:
1 | curl http://127.0.0.1:8095/old/test |
预期仍在 /old/ location 内处理,不会重新进入 /new/ location。
23. 可维护配置实践
23.1 推荐目录拆分
1 | /etc/nginx/nginx.conf |
建议:
nginx.conf只放全局通用配置。conf.d/*.conf每个文件对应一个站点或一类入口。- 可复用片段放在
snippets。 - 文件名用数字或业务名控制顺序和含义。
- 默认 server 显式命名为
00-default.conf。
23.2 推荐 server 结构
1 | server { |
23.3 配置书写原则
- 先写默认 server,再写业务 server。
- 先写精确 location,再写特殊前缀,再写兜底
/。 - 静态资源路径优先使用
^~避免被正则误伤。 - 能用
return就不用rewrite。 - 能用清晰前缀匹配就不用正则。
root与alias不要混淆,使用alias时保持末尾斜杠一致。- 每个站点配置独立 access log 和 error log,便于排查。
- 每次修改配置后执行
nginx -t,再 reload。
24. 常见误区与反例
24.1 误以为 server_name _ 是通配符
错误理解:
1 | server_name _ 可以匹配所有域名 |
正确理解:
1 | _ 只是普通名称,兜底效果来自 default_server |
24.2 误以为最长前缀一定最终生效
普通最长前缀只是候选。如果后续正则 location 命中,正则会覆盖普通前缀。除非最长前缀使用 ^~。
24.3 root 与 alias 混用
错误:
1 | location /static/ { |
建议:
1 | location /static/ { |
24.4 前端 history 路由吞掉静态资源 404
问题配置:
1 | location / { |
如果所有资源都走这个规则,不存在的 JS/CSS/图片也可能返回 index.html。建议静态资源单独配置:
1 | location ^~ /assets/ { |
24.5 rewrite 过度使用
不推荐:
1 | rewrite ^/(.*)$ https://www.example.com/$1 permanent; |
推荐:
1 | return 301 https://www.example.com$request_uri; |
25. 第二阶段必做实验清单
- 配置多个
server_name,验证 Host 匹配规则。 - 配置
default_server,验证未知 Host 进入默认 server。 - 配置精确、前缀、
^~、正则 location,验证完整优先级。 - 对比
root和alias在不同 URI 下的文件路径拼接结果。 - 使用
try_files $uri $uri/ =404配置普通静态站点。 - 使用
try_files $uri $uri/ /index.html支持前端 history 路由。 - 使用
return 301做域名规范化跳转。 - 使用
rewrite last做 URI 内部改写并观察重新匹配。 - 使用
rewrite break验证停留在当前 location 的行为。 - 配置自定义 access log,打印
$uri、$request_uri、$host、$http_host。 - 故意制造 location 误匹配,并通过日志和
return修复。 - 故意制造 root/alias 404,并通过 error log 解释真实文件路径。
26. 第二阶段命令速查表
| 命令 | 作用 |
|---|---|
sudo nginx -t |
检查配置语法 |
sudo nginx -T |
输出完整配置,包含 include 后内容 |
sudo systemctl reload nginx |
平滑加载新配置 |
| `sudo ss -lntp | grep nginx` |
curl -H 'Host: a.test.local' http://127.0.0.1:8088/ |
指定 Host 测试 server 匹配 |
curl -I http://127.0.0.1:8088/path |
查看响应头和跳转 |
curl -v http://127.0.0.1:8088/path |
查看详细请求响应过程 |
sudo tail -f /var/log/nginx/access.log |
实时查看访问日志 |
sudo tail -f /var/log/nginx/error.log |
实时查看错误日志 |
| `sudo nginx -T | grep -n “server_name|location|root|alias”` |
27. 第二阶段知识检查
27.1 配置体系
main、events、http、server、location的职责分别是什么?- 为什么
server不能写在最外层? log_format应该写在哪个上下文?access_log在http、server、location中如何继承和覆盖?- 为什么建议一个站点一个配置文件?
27.2 server 匹配
listen 80 default_server的作用是什么?- 没有显式配置
default_server时,未知 Host 会进入哪里? server_name _是否是通配符?server_name精确匹配、通配符、正则的优先级是什么?$host和$http_host有什么区别?
27.3 location 匹配
location = /的优先级是什么?- 普通前缀和正则 location 谁最终生效?
^~的作用是什么?- 多个正则 location 都能匹配时,哪个生效?
- 为什么建议静态资源目录使用
^~?
27.4 静态路径
root的路径拼接规则是什么?alias的路径拼接规则是什么?alias为什么建议末尾带/?- 访问目录没有 index 文件时为什么常见 403?
- 如何通过 error log 判断真实文件路径?
27.5 try_files 与 rewrite
try_files $uri $uri/ /index.html的执行顺序是什么?try_files的最后一个参数可以是什么?return 301和rewrite ... permanent更推荐哪个?为什么?rewrite last和rewrite break有什么区别?$uri和$request_uri在 rewrite 后可能有什么差异?
28. 第二阶段过关标准
完成本阶段后,你应该达到下面标准:
- 能手写一个多域名、多 location 的 Nginx 配置。
- 能准确解释一次请求最终进入哪个
server和哪个location。 - 能说明
listen、default_server、server_name的关系。 - 能说明
location =、^~、~、~*、普通前缀的优先级。 - 能准确解释
root和alias的文件路径拼接差异。 - 能使用
try_files支持普通静态站点和前端 history 路由。 - 能优先使用
return做清晰跳转,避免 rewrite 滥用。 - 能解释
rewrite last与break的差异。 - 能通过自定义 access log 打印变量,验证匹配和改写是否符合预期。
- 能根据
nginx -T、access log、error log 排查配置匹配问题。
29. 推荐练习顺序
建议按下面顺序练习:
1 | 配置上下文 |
不要一开始就写复杂正则和 rewrite。先把 server、location、root、alias、try_files 这些基础规则练熟。
30. 阶段总结
第二阶段的核心是“请求到底命中了哪段配置”。真正掌握本阶段后,你应该能清楚说明:
1 | 请求进入哪个 listen |
完成本阶段后,再进入第三阶段“反向代理与负载均衡”。那时你已经能稳定判断请求入口和路径匹配,再学习 proxy_pass、upstream、超时、重试和 Header 传递就会更清晰。
