Nginx配置解析、虚拟主机与请求匹配全过程
Nginx配置不是“从上到下遇到哪个location就执行哪个”。配置首先在启动或reload阶段被解析成模块配置;请求到来后,Nginx再根据本地监听地址、SNI、Host、规范化URI、location优先级和请求处理阶段选择动作。只有理解这条链路,才能解释为什么“明明写了location却没命中”“proxy_pass加一个斜杠路径就变了”“alias能访问目录但root一直404”。
学习目标
学完后应能:
- 区分配置解析期和请求运行期。
- 解释main、events、http、upstream、server、location的上下文和继承边界。
- 画出listen、TLS SNI、Host与server_name选择虚拟主机的过程。
- 准确说明精确、前缀、
^~、正则和命名location的选择规则。 - 区分原始请求URI、规范化URI、参数和文件系统路径。
- 解释rewrite、return、last、break和内部重定向。
- 区分root、alias、index和try_files。
- 推导proxy_pass带不带URI时上游实际收到的路径。
- 正确处理代理Header、可信代理、WebSocket、超时和Buffer。
- 使用
nginx -t、nginx -T、debug/error log和最小请求定位配置问题。
一、一条请求的配置决策总链路
flowchart TD
A["Nginx启动或reload解析配置"] --> B["创建监听socket和模块配置"]
B --> C["客户端连接到本地地址与端口"]
C --> D["TLS时结合SNI选择初始配置"]
D --> E["读取请求行与Host"]
E --> F["选择server虚拟主机"]
F --> G["规范化URI"]
G --> H["执行server级rewrite"]
H --> I["选择location"]
I --> J["执行location级rewrite与访问控制"]
J --> K{"内容处理器"}
K -- "静态文件" --> L["root或alias映射文件"]
K -- "反向代理" --> M["proxy_pass访问upstream"]
K -- "直接响应" --> N["return生成响应"]
L --> O["Header与Body Filter"]
M --> O
N --> O
O --> P["写回客户端并记录日志"]配置文件的书写顺序只在某些指令中有决定作用,例如正则location和正则server_name;它不是一门通用的逐行脚本语言。
二、配置解析期发生什么
执行:
nginx -tNginx大致会:
flowchart TD
A["读取nginx.conf"] --> B["展开include文件"]
B --> C["词法与语法解析"]
C --> D["检查指令是否允许出现在当前上下文"]
D --> E["各模块创建main/server/location配置"]
E --> F["合并父子级配置"]
F --> G["尝试打开日志、证书和引用文件"]
G --> H["检查监听地址与其他运行条件"]
H --> I{"是否全部成功"}
I -- "否" --> J["输出文件、行号和错误"]
I -- "是" --> K["syntax is ok / test is successful"]nginx -t 不发送真实业务流量,不能证明:
- upstream能连接。
- DNS能解析动态后端。
- 证书客户端兼容性正确。
- location符合业务预期。
- 文件权限在所有运行阶段都正确。
- 性能和容量满足生产流量。
但它是reload前最低限度的安全门槛。
打印最终展开配置:
nginx -T它比只看某个源文件可靠,因为 include、镜像默认配置和环境部署路径可能让实际配置与预期不同。输出可能包含域名、内部IP、证书路径等信息,分享前要脱敏。
三、配置上下文与作用域
flowchart TD
A["main全局上下文"] --> B["events"]
A --> C["http"]
C --> D["upstream"]
C --> E["server"]
E --> F["location"]
F --> G["嵌套location或命名location"]| 上下文 | 负责什么 | 常见指令 |
|---|---|---|
| main | 进程、用户、PID、全局日志 | user、worker_processes、error_log |
| events | Worker连接事件 | worker_connections、use |
| http | HTTP模块公共配置 | log_format、gzip、map、include |
| upstream | 后端服务组 | server、keepalive、策略参数 |
| server | 一个虚拟主机 | listen、server_name、TLS配置 |
| location | 某类URI处理规则 | root、proxy_pass、try_files |
指令只能出现在文档允许的上下文。把 worker_connections 放进http,或把 server_name 放进events,会在解析期失败。
四、继承不是简单复制父配置
很多指令支持在http、server、location多层配置,但继承规则由具体模块定义,不能概括成“子级自动叠加父级”。常见模式包括:
- 子级未配置时继承父级值。
- 子级一旦配置该指令,就整体覆盖父级配置。
- 数组型指令可能不与父级数组合并。
- 某些指令只在特定上下文生效。
- rewrite模块有自己的执行顺序。
例如下面这种“父级两个Header、子级再增加一个”的写法容易被误解:
http {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
server {
location /api/ {
proxy_set_header X-Request-Id $request_id;
proxy_pass http://order_api;
}
}
}proxy_set_header在当前层一旦出现,父级同类指令通常不会按“Map逐项合并”的直觉继续继承;上例可能导致location只保留当前层重新声明的集合。更清晰的做法是把完整Header集合放到片段中,在每个代理location显式include:
# /etc/nginx/snippets/proxy-headers.conf
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 X-Request-Id $request_id;location /api/ {
include /etc/nginx/snippets/proxy-headers.conf;
proxy_pass http://order_api;
}应通过当前版本文档和 nginx -T验证最终配置,并让后端回显实际Header。
五、一个可维护的基础结构
主配置:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr [$time_local] "$request" '
'status=$status bytes=$body_bytes_sent '
'request_time=$request_time '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_time=$upstream_response_time '
'request_id=$request_id';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 30s;
include /etc/nginx/conf.d/*.conf;
}站点配置拆到 conf.d,便于版本管理、测试和按域名审查。拆文件不会创造新的作用域;include只是把内容插入当前位置解析,因此被include文件中的指令必须适合include所在上下文。
六、连接先按listen分组
请求首先到达某个本地IP与端口:
server {
listen 80;
server_name api.example.com;
}
server {
listen 8080;
server_name admin.example.com;
}访问宿主机80时,不会去8080这一组server中匹配server_name。
listen还可能包含:
- 明确IP或通配地址。
- IPv4和IPv6。
ssl。http2相关版本写法。default_server。reuseport等监听参数。
具体语法会随Nginx版本变化,升级时以目标版本文档和 nginx -t为准。
七、default_server属于listen,不属于server_name
server {
listen 80 default_server;
server_name _;
return 444;
}
server {
listen 80;
server_name api.example.com;
}当Host没有匹配任何server_name时,请求落到该地址端口的default server。server_name _本身没有神奇的默认语义,真正指定默认的是 listen ... default_server;下划线只是常用的不期望正常匹配的名称。
默认站点可以拒绝未知Host,减少请求误落到业务站点,但返回码和审计策略应结合负载均衡、健康检查及安全规范设计。
八、TLS、SNI与Host不是同一个阶段
HTTPS连接中,TLS握手发生在HTTP请求Header之前。客户端可以在TLS ClientHello中通过SNI提供域名,让Nginx选择证书和部分TLS配置;握手成功后才读取HTTP Host。
flowchart TD
A["TCP连接443"] --> B["TLS ClientHello携带SNI"]
B --> C["按监听地址与SNI选择证书配置"]
C --> D["完成TLS握手"]
D --> E["读取HTTP请求行和Host"]
E --> F["再次完成HTTP虚拟主机选择"]因此:
- 无SNI老客户端可能拿到默认server证书。
- SNI域名与HTTP Host不一致需要安全策略。
- 证书必须覆盖用户实际访问域名。
- 仅修改server_name不能修复证书域名不匹配。
九、server_name匹配优先级
在已确定的listen地址端口组内,可按下面思路理解:
- 精确名称,例如
api.example.com。 - 以通配符开头的最长匹配,例如
*.example.com。 - 以通配符结尾的最长匹配,例如
mail.*。 - 按配置顺序第一个匹配的正则名称。
- 都不匹配则使用该listen组的default server。
server_name api.example.com;
server_name *.example.com;
server_name ~^api\d+\.example\.com$;正则server_name需要谨慎:执行成本和可读性更差,顺序会影响结果。能用精确名称或标准通配符时优先不用正则。
十、URI、参数和原始请求不能混淆
假设请求:
GET /api/a/../orders/%31%30%30?trace=abc HTTP/1.1需要区分:
| 值 | 含义 |
|---|---|
$request | 完整原始请求行 |
$request_uri | 原始URI,通常包含原始参数,适合日志和原样跳转 |
$uri | 当前规范化URI,内部重定向后可能变化,不包含参数 |
$args | 当前查询参数字符串 |
$arg_name | 某个查询参数 |
Nginx匹配location时使用规范化后的URI。规范化可能涉及百分号解码、处理 ./..、合并斜杠等行为;具体还受配置和版本影响。
不要把未经约束的URI直接拼文件路径、跳转地址或安全判断。应用和代理层对解码次数不一致还可能产生路径穿越或鉴权绕过,需要端到端测试。
十一、location类型
location = /health { }
location ^~ /assets/ { }
location /api/ { }
location ~ \.php$ { }
location ~* \.(jpg|png)$ { }
location @fallback { }| 类型 | 含义 |
|---|---|
= | 精确匹配规范化URI |
| 无修饰符 | 普通前缀匹配 |
^~ | 前缀匹配;成为最长前缀时跳过同级正则检查 |
~ | 区分大小写正则 |
~* | 不区分大小写正则 |
@name | 命名location,仅用于内部跳转,不能被普通URI直接匹配 |
十二、location选择算法
适合初学和大多数非嵌套配置的流程:
flowchart TD
A["规范化URI"] --> B{"是否有精确location = 匹配"}
B -- "是" --> C["立即选择精确location"]
B -- "否" --> D["寻找最长前缀location"]
D --> E{"最长前缀是否带^~"}
E -- "是" --> F["选择该前缀并跳过同级正则"]
E -- "否" --> G["按配置顺序检查正则location"]
G --> H{"是否有正则匹配"}
H -- "是" --> I["选择第一个匹配正则"]
H -- "否" --> J["选择先前记录的最长前缀"]正则location不是“最长正则获胜”,而是按配置顺序第一个匹配获胜。嵌套location还有额外查找细节,很容易产生反直觉结果;商业配置优先使用扁平、明确、可测试的规则。
十三、location匹配实验
server {
listen 8080;
location = /api {
return 200 "exact /api\n";
}
location ^~ /assets/ {
return 200 "prefix assets\n";
}
location / {
return 200 "prefix root\n";
}
location ~* \.(jpg|png)$ {
return 200 "regex image\n";
}
}验证:
curl -i http://127.0.0.1:8080/api
curl -i http://127.0.0.1:8080/api/
curl -i http://127.0.0.1:8080/a.PNG
curl -i http://127.0.0.1:8080/assets/a.PNG预期:
/api命中精确location。/api/不等于/api,回到最长前缀/。/a.PNG命中不区分大小写图片正则。/assets/a.PNG由^~ /assets/拦截,不再检查同级图片正则。
十四、rewrite模块的执行循环
简化流程:
flowchart TD
A["执行server级rewrite指令"] --> B["选择location"]
B --> C["执行location级rewrite指令"]
C --> D{"URI是否被last等方式改写"}
D -- "否" --> E["继续当前请求阶段"]
D -- "是" --> F["使用新URI重新选择location"]
F --> B为防止错误规则无限循环,内部URI重写循环有次数保护。不要依赖上限救场,应设计单向、可证明终止的规则。
return
直接结束当前处理并返回状态或跳转:
return 301 https://api.example.com$request_uri;简单跳转优先使用return,通常比正则rewrite更清楚。
rewrite ... last
修改URI并重新进行location匹配:
rewrite ^/old/(.*)$ /new/$1 last;rewrite ... break
停止当前rewrite指令集,通常不按last方式重新走新的location匹配。它与proxy_pass组合时路径行为容易复杂,必须通过实际请求和上游日志验证。
十五、外部重定向和内部重定向
外部重定向:
Nginx返回301/302和Location
→ 浏览器收到响应
→ 浏览器发起第二次请求内部重定向:
Nginx内部把URI改为另一个URI或命名location
→ 客户端不知道
→ 同一个请求继续处理try_files、error_page、rewrite last等都可能触发内部重定向。排查“一个请求为什么经过多个location”时要考虑内部跳转,而不是只看原始URI。
十六、root如何计算文件路径
location /static/ {
root /data/www;
}请求:
/static/css/app.css典型文件路径:
/data/www + /static/css/app.css
= /data/www/static/css/app.cssroot通常把规范化URI追加到root目录。出现404时应打印/推导完整路径,再检查文件存在性、目录执行权限、Nginx用户、SELinux和挂载。
十七、alias为什么不同
location /downloads/ {
alias /data/files/;
}请求:
/downloads/report.pdf典型映射:
/data/files/report.pdfalias用目标目录替换匹配的location前缀,不是简单把完整URI追加到目录。目录型location与alias尾部斜杠不一致是高频404原因。
对比:
| 请求 | 配置 | 结果思路 |
|---|---|---|
/static/a.css | root /data/www | /data/www/static/a.css |
/static/a.css | alias /data/www/ | /data/www/a.css |
正则location中使用alias还需要捕获组,复杂度更高;没有明确必要时优先使用清晰的前缀location。
十八、index不是无条件替换URI
location / {
root /data/www;
index index.html index.htm;
}index用于处理以目录结尾的请求,查找索引文件。找到后可能触发内部处理。它不能代替SPA的任意路由回退,也不能解决错误root路径。
十九、try_files逐项检查什么
location / {
root /data/www/dist;
try_files $uri $uri/ /index.html;
}典型过程:
flowchart TD
A["请求URI"] --> B["按root检查$uri文件"]
B --> C{"文件存在"}
C -- "是" --> D["返回该文件"]
C -- "否" --> E["检查$uri目录"]
E --> F{"目录存在"}
F -- "是" --> G["按目录/index规则处理"]
F -- "否" --> H["内部重定向到/index.html"]最后一个参数是回退目标,不一定是普通文件检查项。它可以是状态码、URI或命名location,具体语法以当前版本文档为准。
SPA应只在前端location使用回退。不要让 /api/orders 的404也回退成HTML 200,否则调用方会收到“状态成功但内容是index.html”的伪成功。
二十、proxy_pass的三个组成
proxy_pass http://order_api/v1/;包含:
- 协议:
http。 - 上游地址或upstream名称:
order_api。 - 可选URI部分:
/v1/。
是否写URI部分,会影响Nginx如何构造上游请求路径。
二十一、proxy_pass不带URI
location /api/ {
proxy_pass http://order_api;
}请求:
/api/orders/1001?trace=abc典型结果是把当前请求URI传给上游:
/api/orders/1001?trace=abc如果前面发生rewrite,实际转发URI要结合当前URI和Nginx版本语义验证,不能只看最初浏览器地址。
二十二、proxy_pass带URI
location /api/ {
proxy_pass http://order_api/v1/;
}对普通前缀location,可以理解为用proxy_pass中的URI替换匹配到的规范化location前缀:
原请求:/api/orders/1001
匹配前缀:/api/
剩余部分:orders/1001
上游URI前缀:/v1/
结果:/v1/orders/1001一个尾部斜杠就可能改变上游路径,因此必须在后端访问日志或回显服务中验证。
二十三、proxy_pass路径实验矩阵
| location | proxy_pass | 请求 | 典型上游路径 |
|---|---|---|---|
/api/ | http://upstream | /api/a | /api/a |
/api/ | http://upstream/ | /api/a | /a |
/api/ | http://upstream/v1/ | /api/a | /v1/a |
这些结论适用于常见前缀location。正则location、命名location、变量形式proxy_pass、rewrite后的URI都有额外规则和限制。复杂组合应拆分配置,避免靠记忆猜测。
二十四、正则location中的proxy_pass限制
正则匹配没有一个固定、可直接替换的字符串前缀,因此proxy_pass携带URI时行为更难推导,部分写法会被Nginx拒绝。常用做法是:
- 正则location中proxy_pass不携带URI部分。
- 先用rewrite明确得到目标URI,再代理。
- 尽量改成普通前缀location。
每次都执行:
nginx -t
nginx -T并使用上游回显实际URI。
二十五、变量形式proxy_pass与DNS
resolver 127.0.0.11 valid=30s;
set $backend "http://app:8080";
proxy_pass $backend;使用变量会改变配置解析、URI拼接和域名解析行为。运行期域名解析需要正确的resolver;在Docker自定义网络中 127.0.0.11常是内置DNS,但应以实际环境验证。
变量不是实现动态服务发现的万能方法,还要处理:
- DNS TTL。
- 解析失败。
- 多地址。
- 容器重建。
- upstream连接复用。
- 代理URI变化。
- 外部DNS与内部服务名边界。
二十六、upstream与连接复用
upstream order_api {
server 10.0.1.11:8080;
server 10.0.1.12:8080;
keepalive 64;
}
location /api/ {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://order_api/;
}upstream keepalive缓存的是每个Worker可复用的空闲上游连接,不是限制总连接数,也不是业务连接池的全局上限。Worker多时总空闲连接可能放大,后端还要配置合理keep-alive和容量。
二十七、代理Header与可信边界
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 X-Request-Id $request_id;用途:
| Header | 用途 |
|---|---|
| Host | 让后端知道用户访问域名 |
| X-Real-IP | 传递Nginx当前认定的客户端地址 |
| X-Forwarded-For | 追加代理链 |
| X-Forwarded-Proto | 告诉后端外部协议 |
| X-Request-Id | 关联入口与后端日志 |
客户端可以伪造这些Header。Nginx只有在请求确实来自受信负载均衡/CDN网段时,才应使用realip模块接收其X-Forwarded-For:
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;示例CIDR必须替换为真实代理出口。不能直接取X-Forwarded-For第一个地址作为真实IP,否则限流、审计和风控可被绕过。
二十八、WebSocket为什么需要Upgrade
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /ws/ {
proxy_pass http://socket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 60s;
}WebSocket先通过HTTP握手升级协议。缺少Upgrade/Connection处理时,普通HTTP接口可能正常,但WebSocket握手失败。长连接还会占FD、内存和Worker连接槽位,并影响平滑退出时间。
二十九、代理超时分别约束什么
proxy_connect_timeout 3s;
proxy_send_timeout 15s;
proxy_read_timeout 30s;| 指令 | 约束阶段 | 超时常见根因 |
|---|---|---|
| connect | 与upstream建连 | 端口、路由、防火墙、SYN队列 |
| send | 向upstream发送长期无进展 | 后端不读、网络阻塞、大请求 |
| read | 从upstream读取长期无进展 | 慢SQL、线程池、连接池、下游慢 |
不能看到504就把read timeout无限增大。超时是失败边界,根因可能在Java、数据库或远程服务。
三十、proxy buffering与流式响应
普通API响应常适合缓冲,让后端尽快释放连接,并隔离慢客户端:
proxy_buffering on;SSE、实时流和部分大文件场景可能需要低延迟传输:
proxy_buffering off;关闭后慢客户端可能长期占用upstream连接。Buffer大小、临时文件和磁盘IO应结合响应大小与并发压测,不能全局机械关闭。
三十一、HTTPS基础安全配置
server {
listen 80;
server_name api.example.com;
return 301 https://api.example.com$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://order_api;
}
}TLS 1.0/1.1已经淘汰,不应出现在新系统基线。Cipher、Session、OCSP和安全Header需要结合Nginx/OpenSSL版本、客户端范围和企业安全扫描维护,不能永久复制一串旧Cipher。
三十二、完整可运行匹配Demo
目录:
nginx-match-lab/
├── nginx.conf
└── html/
├── index.html
└── static/
└── app.cssnginx.conf:
events {
worker_connections 1024;
}
http {
log_format lab '$request status=$status uri=$uri args=$args request_time=$request_time';
access_log /dev/stdout lab;
error_log /dev/stderr notice;
server {
listen 8080 default_server;
server_name _;
location = /health {
return 200 "healthy\n";
}
location ^~ /static/ {
root /data/www;
}
location /debug/ {
return 200 "uri=$uri args=$args\n";
}
location / {
root /data/www;
try_files $uri $uri/ /index.html;
}
location ~* \.(jpg|png)$ {
return 200 "image regex\n";
}
}
}启动方式可使用本机Nginx,也可挂载到 Nginx容器化专题 的受控Demo。验证矩阵:
nginx -t -c /absolute/path/nginx.conf
curl -i http://127.0.0.1:8080/health
curl -i 'http://127.0.0.1:8080/debug/a?trace=1'
curl -i http://127.0.0.1:8080/static/app.css
curl -i http://127.0.0.1:8080/static/missing.png
curl -i http://127.0.0.1:8080/front/route要逐个预测命中的location、文件路径、是否发生内部重定向和最终状态,再对照access/error log。
三十三、配置发布流程
flowchart TD
A["配置进入Git与代码评审"] --> B["CI执行nginx -t"]
B --> C["运行server/location路由测试"]
C --> D["检查TLS、安全头和敏感信息"]
D --> E["金丝雀实例应用配置"]
E --> F["外部验证域名、路由和upstream"]
F --> G{"错误率、延迟是否正常"}
G -- "否" --> H["回滚配置并保留日志"]
G -- "是" --> I["分批reload并观察Worker"]仅 nginx -t通过不能证明路由业务语义正确。至少要为关键Host、URI、方法、Header和参数建立自动化请求矩阵。
三十四、配置不生效排查
nginx -V
nginx -t
nginx -T
ps -ef | grep '[n]ginx'检查:
- 操作的是哪台主机或容器。
- master实际使用哪个prefix与config path。
- 源文件是否被include。
- 是否修改了被挂载遮住的错误文件。
nginx -t是否使用同一二进制、用户和配置。- reload是否成功,error log是否报错。
- 请求是否命中另一listen、server或location。
- CDN、浏览器或上游缓存是否返回旧结果。
三十五、404与403怎么区分
404
常见原因:
- server或location未命中预期规则。
- root拼接后的文件不存在。
- alias尾部斜杠错误。
- proxy_pass改写成错误上游URI。
- SPA缺少try_files。
- 内部重定向落到错误location。
403
常见原因:
- 文件存在但Nginx用户无读取权限。
- 父目录缺少执行权限。
- SELinux策略阻止。
- 目录请求没有index且autoindex关闭。
- deny/access规则拒绝。
- 容器挂载权限或只读边界错误。
先看error log中的最终文件路径和系统错误,不要用 chmod 777掩盖权限模型。
三十六、502与504从配置角度怎么查
502
- upstream名称解析失败。
- IP或端口错误。
- 后端只监听127.0.0.1。
- HTTP代理到HTTPS端口或反之。
- 连接被拒绝。
- 后端提前关闭或返回无效响应。
504
- 建连或读取超过超时。
- 后端慢SQL。
- Java线程池或连接池排队。
- 下游调用慢。
- 网络丢包和静默丢弃。
日志应同时记录 $request_time、$upstream_connect_time、$upstream_header_time、$upstream_response_time和upstream地址/status,才能区分时间花在哪一段。
三十七、商业场景一:API返回HTML 200
现象:前端调用 /api/orders,HTTP状态200,但JSON解析失败,响应是index.html。
错误配置把SPA回退放到了覆盖API的location:
location / {
try_files $uri $uri/ /index.html;
}且没有更明确的 /api/代理规则,或API内部重定向落回 /。
修复:API使用独立前缀location,失败保持真实HTTP状态;SPA回退只服务前端路由,并建立Content-Type与状态断言测试。
三十八、商业场景二:加斜杠后接口全404
发布前:
location /api/ {
proxy_pass http://order_api;
}后端收到 /api/orders。发布后改成:
proxy_pass http://order_api/;后端改为收到 /orders,而应用Controller只映射 /api/orders,因此404。
修复不是盲目加回斜杠,而是明确外部路由和内部路由契约,通过上游回显与自动化用例验证。
三十九、商业场景三:伪造IP绕过限流
错误做法:直接取任意请求的X-Forwarded-For第一个值作为客户端IP。攻击者每次请求填写不同IP,限流Key不断变化。
正确做法:
- 只信任负载均衡/CDN的明确出口网段。
- 使用realip模块按代理链还原地址。
- 未经可信代理进入的Header视为客户端输入。
- 限流、日志和应用审计使用同一可信地址口径。
- 定期同步代理网段并测试变更。
四十、面试标准回答
Nginx如何选择server和location
先按连接到达的本地IP和端口确定listen组;HTTPS握手阶段还会结合SNI选择证书配置,读取HTTP Host后按精确名称、通配符和正则优先级选择server,未匹配则进入该listen组的default server。之后对URI规范化,精确location优先;否则记录最长前缀,若它带^~则跳过同级正则,否则按配置顺序选择第一个匹配正则,正则不匹配才使用最长前缀。
root和alias有什么区别
root通常把完整规范化URI追加到根目录,例如root
/data/www加URI/static/a.css得到/data/www/static/a.css;alias用目标目录替换匹配的location前缀,例如location/static/配alias/data/www/得到/data/www/a.css。alias尾部斜杠、正则捕获和权限是高频错误点。
proxy_pass带不带斜杠有什么区别
本质不是只看一个斜杠,而是proxy_pass是否包含URI部分。普通前缀location中,不带URI通常把当前请求URI传给上游;带URI时用该URI替换匹配的location前缀。正则、命名location、变量和rewrite组合有额外规则,必须用后端日志或回显服务验证实际上游路径。
rewrite last和break有什么区别
last修改URI后重新进行location匹配,可能进入另一套处理规则;break停止当前rewrite指令集,通常继续当前location后续处理,不按last方式重新选location。复杂循环有次数保护,但配置应设计为单向终止,简单跳转优先使用return。
为什么不能直接信任X-Forwarded-For
它是普通HTTP Header,客户端可以伪造。只有请求来自明确可信代理网段时,才能由realip模块按代理链还原客户端地址;否则按其内容限流、审计或鉴权会被绕过。可信代理CIDR必须来自真实LB/CDN配置并持续维护。
nginx -t成功是否表示发布一定安全
不是。它主要证明配置语法、上下文和部分引用资源可加载,不证明upstream可达、DNS正确、路由符合业务、TLS客户端兼容或容量足够。生产还要执行Host/URI/Header请求矩阵、外部TLS检查、金丝雀和指标观察。
四十一、学习验收
- 画出配置解析期与请求运行期。
- 解释include为什么不产生新作用域。
- 给定多个listen/server_name,推导请求进入哪个server。
- 给定精确、前缀、^~和正则location,逐个推导匹配结果。
- 区分
$request_uri、$uri和$args。 - 用实验演示rewrite last重新匹配location,break不按同一路径重选。
- 对同一URI分别计算root和alias文件路径。
- 解释try_files每个检查项和最终内部重定向。
- 完成三组proxy_pass路径矩阵并在上游日志验证。
- 配置可信代理网段,证明客户端伪造XFF不会改变真实IP口径。
- 配置WebSocket Upgrade并观察长连接。
- 使用request/upstream时间变量区分Nginx慢和后端慢。
- 完成
nginx -t、路由测试、金丝雀、reload和回滚流程。 - 分别定位一次404、403、502和504。
