Skip to content

Nginx配置解析、虚拟主机与请求匹配全过程

Nginx配置不是“从上到下遇到哪个location就执行哪个”。配置首先在启动或reload阶段被解析成模块配置;请求到来后,Nginx再根据本地监听地址、SNI、Host、规范化URI、location优先级和请求处理阶段选择动作。只有理解这条链路,才能解释为什么“明明写了location却没命中”“proxy_pass加一个斜杠路径就变了”“alias能访问目录但root一直404”。

学习目标

学完后应能:

  1. 区分配置解析期和请求运行期。
  2. 解释main、events、http、upstream、server、location的上下文和继承边界。
  3. 画出listen、TLS SNI、Host与server_name选择虚拟主机的过程。
  4. 准确说明精确、前缀、^~、正则和命名location的选择规则。
  5. 区分原始请求URI、规范化URI、参数和文件系统路径。
  6. 解释rewrite、return、last、break和内部重定向。
  7. 区分root、alias、index和try_files。
  8. 推导proxy_pass带不带URI时上游实际收到的路径。
  9. 正确处理代理Header、可信代理、WebSocket、超时和Buffer。
  10. 使用 nginx -tnginx -T、debug/error log和最小请求定位配置问题。

一、一条请求的配置决策总链路

mermaid
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;它不是一门通用的逐行脚本语言。

二、配置解析期发生什么

执行:

bash
nginx -t

Nginx大致会:

mermaid
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前最低限度的安全门槛。

打印最终展开配置:

bash
nginx -T

它比只看某个源文件可靠,因为 include、镜像默认配置和环境部署路径可能让实际配置与预期不同。输出可能包含域名、内部IP、证书路径等信息,分享前要脱敏。

三、配置上下文与作用域

mermaid
flowchart TD
    A["main全局上下文"] --> B["events"]
    A --> C["http"]
    C --> D["upstream"]
    C --> E["server"]
    E --> F["location"]
    F --> G["嵌套location或命名location"]
上下文负责什么常见指令
main进程、用户、PID、全局日志userworker_processeserror_log
eventsWorker连接事件worker_connectionsuse
httpHTTP模块公共配置log_formatgzipmapinclude
upstream后端服务组serverkeepalive、策略参数
server一个虚拟主机listenserver_name、TLS配置
location某类URI处理规则rootproxy_passtry_files

指令只能出现在文档允许的上下文。把 worker_connections 放进http,或把 server_name 放进events,会在解析期失败。

四、继承不是简单复制父配置

很多指令支持在http、server、location多层配置,但继承规则由具体模块定义,不能概括成“子级自动叠加父级”。常见模式包括:

  • 子级未配置时继承父级值。
  • 子级一旦配置该指令,就整体覆盖父级配置。
  • 数组型指令可能不与父级数组合并。
  • 某些指令只在特定上下文生效。
  • rewrite模块有自己的执行顺序。

例如下面这种“父级两个Header、子级再增加一个”的写法容易被误解:

nginx
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:

nginx
# /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;
nginx
location /api/ {
    include /etc/nginx/snippets/proxy-headers.conf;
    proxy_pass http://order_api;
}

应通过当前版本文档和 nginx -T验证最终配置,并让后端回显实际Header。

五、一个可维护的基础结构

主配置:

nginx
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与端口:

nginx
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

nginx
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。

mermaid
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地址端口组内,可按下面思路理解:

  1. 精确名称,例如 api.example.com
  2. 以通配符开头的最长匹配,例如 *.example.com
  3. 以通配符结尾的最长匹配,例如 mail.*
  4. 按配置顺序第一个匹配的正则名称。
  5. 都不匹配则使用该listen组的default server。
nginx
server_name api.example.com;
server_name *.example.com;
server_name ~^api\d+\.example\.com$;

正则server_name需要谨慎:执行成本和可读性更差,顺序会影响结果。能用精确名称或标准通配符时优先不用正则。

十、URI、参数和原始请求不能混淆

假设请求:

text
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类型

nginx
location = /health { }
location ^~ /assets/ { }
location /api/ { }
location ~ \.php$ { }
location ~* \.(jpg|png)$ { }
location @fallback { }
类型含义
=精确匹配规范化URI
无修饰符普通前缀匹配
^~前缀匹配;成为最长前缀时跳过同级正则检查
~区分大小写正则
~*不区分大小写正则
@name命名location,仅用于内部跳转,不能被普通URI直接匹配

十二、location选择算法

适合初学和大多数非嵌套配置的流程:

mermaid
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匹配实验

nginx
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";
    }
}

验证:

bash
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模块的执行循环

简化流程:

mermaid
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

直接结束当前处理并返回状态或跳转:

nginx
return 301 https://api.example.com$request_uri;

简单跳转优先使用return,通常比正则rewrite更清楚。

rewrite ... last

修改URI并重新进行location匹配:

nginx
rewrite ^/old/(.*)$ /new/$1 last;

rewrite ... break

停止当前rewrite指令集,通常不按last方式重新走新的location匹配。它与proxy_pass组合时路径行为容易复杂,必须通过实际请求和上游日志验证。

十五、外部重定向和内部重定向

外部重定向:

text
Nginx返回301/302和Location
→ 浏览器收到响应
→ 浏览器发起第二次请求

内部重定向:

text
Nginx内部把URI改为另一个URI或命名location
→ 客户端不知道
→ 同一个请求继续处理

try_fileserror_page、rewrite last等都可能触发内部重定向。排查“一个请求为什么经过多个location”时要考虑内部跳转,而不是只看原始URI。

十六、root如何计算文件路径

nginx
location /static/ {
    root /data/www;
}

请求:

text
/static/css/app.css

典型文件路径:

text
/data/www + /static/css/app.css
= /data/www/static/css/app.css

root通常把规范化URI追加到root目录。出现404时应打印/推导完整路径,再检查文件存在性、目录执行权限、Nginx用户、SELinux和挂载。

十七、alias为什么不同

nginx
location /downloads/ {
    alias /data/files/;
}

请求:

text
/downloads/report.pdf

典型映射:

text
/data/files/report.pdf

alias用目标目录替换匹配的location前缀,不是简单把完整URI追加到目录。目录型location与alias尾部斜杠不一致是高频404原因。

对比:

请求配置结果思路
/static/a.cssroot /data/www/data/www/static/a.css
/static/a.cssalias /data/www//data/www/a.css

正则location中使用alias还需要捕获组,复杂度更高;没有明确必要时优先使用清晰的前缀location。

十八、index不是无条件替换URI

nginx
location / {
    root /data/www;
    index index.html index.htm;
}

index用于处理以目录结尾的请求,查找索引文件。找到后可能触发内部处理。它不能代替SPA的任意路由回退,也不能解决错误root路径。

十九、try_files逐项检查什么

nginx
location / {
    root /data/www/dist;
    try_files $uri $uri/ /index.html;
}

典型过程:

mermaid
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的三个组成

nginx
proxy_pass http://order_api/v1/;

包含:

  • 协议:http
  • 上游地址或upstream名称:order_api
  • 可选URI部分:/v1/

是否写URI部分,会影响Nginx如何构造上游请求路径。

二十一、proxy_pass不带URI

nginx
location /api/ {
    proxy_pass http://order_api;
}

请求:

text
/api/orders/1001?trace=abc

典型结果是把当前请求URI传给上游:

text
/api/orders/1001?trace=abc

如果前面发生rewrite,实际转发URI要结合当前URI和Nginx版本语义验证,不能只看最初浏览器地址。

二十二、proxy_pass带URI

nginx
location /api/ {
    proxy_pass http://order_api/v1/;
}

对普通前缀location,可以理解为用proxy_pass中的URI替换匹配到的规范化location前缀:

text
原请求:/api/orders/1001
匹配前缀:/api/
剩余部分:orders/1001
上游URI前缀:/v1/
结果:/v1/orders/1001

一个尾部斜杠就可能改变上游路径,因此必须在后端访问日志或回显服务中验证。

二十三、proxy_pass路径实验矩阵

locationproxy_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。

每次都执行:

bash
nginx -t
nginx -T

并使用上游回显实际URI。

二十五、变量形式proxy_pass与DNS

nginx
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与连接复用

nginx
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与可信边界

nginx
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:

nginx
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

nginx
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连接槽位,并影响平滑退出时间。

二十九、代理超时分别约束什么

nginx
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响应常适合缓冲,让后端尽快释放连接,并隔离慢客户端:

nginx
proxy_buffering on;

SSE、实时流和部分大文件场景可能需要低延迟传输:

nginx
proxy_buffering off;

关闭后慢客户端可能长期占用upstream连接。Buffer大小、临时文件和磁盘IO应结合响应大小与并发压测,不能全局机械关闭。

三十一、HTTPS基础安全配置

nginx
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

目录:

text
nginx-match-lab/
├── nginx.conf
└── html/
    ├── index.html
    └── static/
        └── app.css

nginx.conf

nginx
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。验证矩阵:

bash
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。

三十三、配置发布流程

mermaid
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和参数建立自动化请求矩阵。

三十四、配置不生效排查

bash
nginx -V
nginx -t
nginx -T
ps -ef | grep '[n]ginx'

检查:

  1. 操作的是哪台主机或容器。
  2. master实际使用哪个prefix与config path。
  3. 源文件是否被include。
  4. 是否修改了被挂载遮住的错误文件。
  5. nginx -t是否使用同一二进制、用户和配置。
  6. reload是否成功,error log是否报错。
  7. 请求是否命中另一listen、server或location。
  8. 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:

nginx
location / {
    try_files $uri $uri/ /index.html;
}

且没有更明确的 /api/代理规则,或API内部重定向落回 /

修复:API使用独立前缀location,失败保持真实HTTP状态;SPA回退只服务前端路由,并建立Content-Type与状态断言测试。

三十八、商业场景二:加斜杠后接口全404

发布前:

nginx
location /api/ {
    proxy_pass http://order_api;
}

后端收到 /api/orders。发布后改成:

nginx
proxy_pass http://order_api/;

后端改为收到 /orders,而应用Controller只映射 /api/orders,因此404。

修复不是盲目加回斜杠,而是明确外部路由和内部路由契约,通过上游回显与自动化用例验证。

三十九、商业场景三:伪造IP绕过限流

错误做法:直接取任意请求的X-Forwarded-For第一个值作为客户端IP。攻击者每次请求填写不同IP,限流Key不断变化。

正确做法:

  1. 只信任负载均衡/CDN的明确出口网段。
  2. 使用realip模块按代理链还原地址。
  3. 未经可信代理进入的Header视为客户端输入。
  4. 限流、日志和应用审计使用同一可信地址口径。
  5. 定期同步代理网段并测试变更。

四十、面试标准回答

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检查、金丝雀和指标观察。

四十一、学习验收

  1. 画出配置解析期与请求运行期。
  2. 解释include为什么不产生新作用域。
  3. 给定多个listen/server_name,推导请求进入哪个server。
  4. 给定精确、前缀、^~和正则location,逐个推导匹配结果。
  5. 区分 $request_uri$uri$args
  6. 用实验演示rewrite last重新匹配location,break不按同一路径重选。
  7. 对同一URI分别计算root和alias文件路径。
  8. 解释try_files每个检查项和最终内部重定向。
  9. 完成三组proxy_pass路径矩阵并在上游日志验证。
  10. 配置可信代理网段,证明客户端伪造XFF不会改变真实IP口径。
  11. 配置WebSocket Upgrade并观察长连接。
  12. 使用request/upstream时间变量区分Nginx慢和后端慢。
  13. 完成 nginx -t、路由测试、金丝雀、reload和回滚流程。
  14. 分别定位一次404、403、502和504。

关联知识点