Nginx `location` 配置说明(后端开发版)

Nginx location 配置说明(后端开发版)

本文面向后端开发人员,重点说明 Nginx 如何选择 location、静态文件如何映射、请求如何转发到后端,以及常见配置风险。

1. 先看原配置

nginx 复制代码
location ~ \.(js|css|png|jpg|jpeg|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|doc|docx|xls|xlsx|ppt|pptx|pdf|txt|xml|webp)$ {
    alias /data/www/htdocs/quickly_fill/page/.output/public;
    try_files $uri $uri/ /site/index.html;
}

这段配置表达的意图是:

  1. URI 以列出的扩展名结尾时,按静态资源处理。
  2. /data/www/htdocs/quickly_fill/page/.output/public 查找文件。
  3. 找不到文件时,尝试目录,最后回退到 /site/index.html

但它有三个值得修正的问题:

  1. 正则 location 中的 alias 没有使用捕获变量。 Nginx 官方要求正则 location 使用 alias 时,正则应包含捕获组,并在 alias 中引用捕获结果。若 URI 与磁盘目录结构一致,更适合使用 root
  2. 静态资源缺失时不应通常回退到 HTML。 例如 /site/app.js 不存在,却返回 index.html,浏览器会因 MIME 类型不符而报错,也会掩盖资源发布问题。静态资源通常应返回 404
  3. 全局按扩展名匹配可能截获后端接口。 例如动态接口 /api/export/report.pdf 也会命中这个正则,导致请求没有进入后端。静态资源规则最好限定在 /site//assets/ 等 URI 前缀下。

另外,~ 是大小写敏感的正则匹配,.JPG 不会命中;需要忽略大小写时使用 ~*

2. Nginx 配置的基本层级

常见配置结构如下:

nginx 复制代码
# 全局上下文
user nginx;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    upstream app_backend {
        server 127.0.0.1:8080;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://app_backend;
        }
    }
}

主要层级:

上下文 作用
全局 worker 数量、运行用户、日志等进程级配置
events Nginx 连接处理模型和连接数
http HTTP 公共配置、MIME、日志、压缩、上游服务等
upstream 定义一个或多个后端实例及负载均衡策略
server 一个虚拟主机,通常按端口和域名区分
location 在一个 server 内按 URI 选择具体处理方式

3. location 的常用写法

3.1 普通前缀匹配

nginx 复制代码
location /api/ {
    proxy_pass http://app_backend;
}

匹配所有以 /api/ 开头的 URI,例如 /api/users

3.2 精确匹配 =

nginx 复制代码
location = /health {
    return 200 "ok\n";
}

只匹配 /health,不匹配 /health//health/detail。精确匹配成功后立即停止查找。

3.3 优先前缀匹配 ^~

nginx 复制代码
location ^~ /api/ {
    proxy_pass http://app_backend;
}

先按前缀匹配;如果它是最长匹配前缀,Nginx 不再检查同级正则 location。当 server 中存在全局扩展名正则,并且必须保证 /api/ 始终进入后端时很有用。

3.4 区分大小写的正则 ~

nginx 复制代码
location ~ \.php$ {
    # 只匹配小写 .php
}

3.5 不区分大小写的正则 ~*

nginx 复制代码
location ~* \.(jpg|jpeg|png|webp)$ {
    # .jpg、.JPG 等都可匹配
}

3.6 命名 location

nginx 复制代码
location / {
    try_files $uri @backend;
}

location @backend {
    proxy_pass http://app_backend;
}

命名 location 不直接匹配外部 URI,一般作为 try_fileserror_page 等指令的内部跳转目标。

4. location 的匹配优先级

可按下面的流程理解:
#mermaid-svg-PNmgjKjrHirUpyRm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PNmgjKjrHirUpyRm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PNmgjKjrHirUpyRm .error-icon{fill:#552222;}#mermaid-svg-PNmgjKjrHirUpyRm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PNmgjKjrHirUpyRm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PNmgjKjrHirUpyRm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .marker.cross{stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PNmgjKjrHirUpyRm p{margin:0;}#mermaid-svg-PNmgjKjrHirUpyRm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label text{fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label span{color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster-label span p{background-color:transparent;}#mermaid-svg-PNmgjKjrHirUpyRm .label text,#mermaid-svg-PNmgjKjrHirUpyRm span{fill:#333;color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .node rect,#mermaid-svg-PNmgjKjrHirUpyRm .node circle,#mermaid-svg-PNmgjKjrHirUpyRm .node ellipse,#mermaid-svg-PNmgjKjrHirUpyRm .node polygon,#mermaid-svg-PNmgjKjrHirUpyRm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .rough-node .label text,#mermaid-svg-PNmgjKjrHirUpyRm .node .label text,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label,#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label{text-anchor:middle;}#mermaid-svg-PNmgjKjrHirUpyRm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .rough-node .label,#mermaid-svg-PNmgjKjrHirUpyRm .node .label,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label,#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label{text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .node.clickable{cursor:pointer;}#mermaid-svg-PNmgjKjrHirUpyRm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .arrowheadPath{fill:#333333;}#mermaid-svg-PNmgjKjrHirUpyRm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PNmgjKjrHirUpyRm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PNmgjKjrHirUpyRm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster text{fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm .cluster span{color:#333;}#mermaid-svg-PNmgjKjrHirUpyRm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PNmgjKjrHirUpyRm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PNmgjKjrHirUpyRm rect.text{fill:none;stroke-width:0;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape p,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PNmgjKjrHirUpyRm .icon-shape .label rect,#mermaid-svg-PNmgjKjrHirUpyRm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNmgjKjrHirUpyRm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PNmgjKjrHirUpyRm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PNmgjKjrHirUpyRm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是









收到请求并规范化 URI
是否命中精确匹配

location = URI
使用精确 location
寻找最长的前缀 location
最长前缀是否带 ^~
使用该前缀 location
按配置出现顺序检查正则 location
是否有正则命中
使用第一个命中的正则 location
是否存在前缀匹配
使用 server 级配置或返回错误
执行静态文件、代理、返回值等处理
是否产生内部重定向
返回响应

可以记成:

  1. 精确匹配 = 优先。
  2. 先记住最长的前缀匹配。
  3. 最长前缀带 ^~ 时,不再检查正则。
  4. 否则按配置书写顺序检查正则,使用第一个匹配项;正则不是按"最长"选择。
  5. 没有正则命中时,使用先前找到的最长前缀。

因此,正则 location 的书写顺序非常重要,应把更具体的正则写在更通用的正则之前。

5. rootalias 的区别

5.1 root:把完整 URI 拼到目录后

nginx 复制代码
location /site/ {
    root /data/www/htdocs/quickly_fill/page/.output/public;
}

请求与文件的映射:

text 复制代码
URI:      /site/assets/app.js
root:     /data/www/htdocs/quickly_fill/page/.output/public
文件路径: /data/www/htdocs/quickly_fill/page/.output/public/site/assets/app.js

当 URI 目录结构和磁盘目录结构一致时,优先使用 root

5.2 alias:用目标目录替换匹配到的 URI 前缀

nginx 复制代码
location /site/ {
    alias /data/www/htdocs/quickly_fill/page/.output/public/;
}

映射结果:

text 复制代码
URI:       /site/assets/app.js
匹配前缀:  /site/
alias:     /data/www/htdocs/quickly_fill/page/.output/public/
文件路径:  /data/www/htdocs/quickly_fill/page/.output/public/assets/app.js

前缀 location 使用 alias 时,路径末尾的 / 最好与 location 保持对应,避免拼接结果异常。

5.3 正则 location 使用 alias

必须显式捕获需要映射的路径:

nginx 复制代码
location ~* ^/downloads/(?<file_path>.+\.(?:pdf|zip))$ {
    alias /data/downloads/$file_path;
}

请求 /downloads/manual/guide.pdf 映射为 /data/downloads/manual/guide.pdf

正则 alias 可读性和维护成本都更高。没有必须替换路径前缀的需求时,建议使用 root

6. try_files 如何工作

nginx 复制代码
try_files $uri $uri/ /site/index.html;

Nginx 按顺序检查:

  1. $uri 对应的文件是否存在。
  2. $uri/ 对应的目录是否存在。
  3. 前面都不存在时,对 /site/index.html 发起内部重定向,重新执行一次 location 匹配。

最后一个参数有特殊意义:

nginx 复制代码
# 找不到就返回 404
try_files $uri =404;

# 找不到就进入命名 location
try_files $uri @backend;

# SPA 路由找不到实际文件时回退到入口页
try_files $uri $uri/ /site/index.html;

try_files 使用当前 rootalias 计算物理路径。配置复杂时,可通过 error_log$request_filename 日志字段确认实际查找路径。

7. 推荐配置:静态站点 + SPA + 后端 API

以下示例假设:

  • 前端访问前缀为 /site/
  • 磁盘上存在 /data/www/htdocs/quickly_fill/page/.output/public/site/index.html
  • 后端监听 127.0.0.1:8080
  • /api/ 原样传递给后端。
nginx 复制代码
upstream quickly_fill_backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    # 后端接口优先处理,避免被同级正则静态规则截获。
    location ^~ /api/ {
        proxy_http_version 1.1;
        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_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;

        # 没有结尾斜杠:后端收到原始 URI,例如 /api/users。
        proxy_pass http://quickly_fill_backend;
    }

    # 对真实静态资源单独处理;文件不存在时明确返回 404。
    location ~* ^/site/.+\.(?:js|css|png|jpe?g|gif|ico|bmp|mp4|mp3|zip|pdf|webp)$ {
        root /data/www/htdocs/quickly_fill/page/.output/public;
        try_files $uri =404;

        expires 7d;
        add_header Cache-Control "public" always;
        access_log off;
    }

    # 没有扩展名的前端路由回退到 SPA 入口页。
    location /site/ {
        root /data/www/htdocs/quickly_fill/page/.output/public;
        try_files $uri $uri/ /site/index.html;
    }

    # 是否跳转到 /site/ 可按项目入口需求调整。
    location = / {
        return 302 /site/;
    }
}

这里故意把"静态资源 404"和"SPA 路由回退"拆开:
#mermaid-svg-M17hnjXQ21S3xzXe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-M17hnjXQ21S3xzXe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-M17hnjXQ21S3xzXe .error-icon{fill:#552222;}#mermaid-svg-M17hnjXQ21S3xzXe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M17hnjXQ21S3xzXe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M17hnjXQ21S3xzXe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .marker.cross{stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M17hnjXQ21S3xzXe p{margin:0;}#mermaid-svg-M17hnjXQ21S3xzXe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label text{fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label span{color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster-label span p{background-color:transparent;}#mermaid-svg-M17hnjXQ21S3xzXe .label text,#mermaid-svg-M17hnjXQ21S3xzXe span{fill:#333;color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .node rect,#mermaid-svg-M17hnjXQ21S3xzXe .node circle,#mermaid-svg-M17hnjXQ21S3xzXe .node ellipse,#mermaid-svg-M17hnjXQ21S3xzXe .node polygon,#mermaid-svg-M17hnjXQ21S3xzXe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .rough-node .label text,#mermaid-svg-M17hnjXQ21S3xzXe .node .label text,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label,#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label{text-anchor:middle;}#mermaid-svg-M17hnjXQ21S3xzXe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .rough-node .label,#mermaid-svg-M17hnjXQ21S3xzXe .node .label,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label,#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label{text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .node.clickable{cursor:pointer;}#mermaid-svg-M17hnjXQ21S3xzXe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .arrowheadPath{fill:#333333;}#mermaid-svg-M17hnjXQ21S3xzXe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M17hnjXQ21S3xzXe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-M17hnjXQ21S3xzXe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster text{fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe .cluster span{color:#333;}#mermaid-svg-M17hnjXQ21S3xzXe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M17hnjXQ21S3xzXe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-M17hnjXQ21S3xzXe rect.text{fill:none;stroke-width:0;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape p,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-M17hnjXQ21S3xzXe .icon-shape .label rect,#mermaid-svg-M17hnjXQ21S3xzXe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-M17hnjXQ21S3xzXe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-M17hnjXQ21S3xzXe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-M17hnjXQ21S3xzXe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} /api/
/site/






请求
URI 前缀
反向代理到后端
是否为静态资源扩展名
文件存在吗
返回静态文件
返回 404
文件或目录存在吗
返回文件或目录入口
内部跳转到 /site/index.html

如果真实入口文件是:

text 复制代码
/data/www/htdocs/quickly_fill/page/.output/public/index.html

也就是磁盘上没有 site 目录,那么需要用 alias 去掉 URI 中的 /site/ 前缀。为了避免缺失的静态资源回退成 HTML,可以拆成三条规则:

nginx 复制代码
# 正则 location 使用 alias 时,通过命名捕获组构造物理路径。
location ~* ^/site/(?<asset_path>.+\.(?:js|css|png|jpe?g|gif|ico|bmp|mp4|mp3|zip|pdf|webp))$ {
    alias /data/www/htdocs/quickly_fill/page/.output/public/$asset_path;
    expires 7d;
    add_header Cache-Control "public" always;
}

# SPA 入口页单独精确映射,并避免缓存旧入口。
location = /site/index.html {
    alias /data/www/htdocs/quickly_fill/page/.output/public/index.html;
    add_header Cache-Control "no-cache" always;
}

# 页面路由对应的文件不存在时,才回退到 SPA 入口页。
location /site/ {
    alias /data/www/htdocs/quickly_fill/page/.output/public/;
    error_page 404 =200 /site/index.html;
}

error_page 404 =200 会把当前 location 产生的 404 内部重定向到 /site/index.html,并将最终状态码改为 200。上线前必须按实际目录结构验证,不应仅根据 URI 猜测文件位置。若可以调整发布目录结构,仍优先推荐前面的 root 方案。

8. 后端反向代理的常见配置

8.1 proxy_pass 是否带结尾 /

这是最常见的路径问题之一。

nginx 复制代码
location /api/ {
    proxy_pass http://127.0.0.1:8080;
}

请求 /api/users?id=1,后端收到 /api/users?id=1

nginx 复制代码
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

请求 /api/users?id=1,匹配前缀 /api/ 被替换为 /,后端收到 /users?id=1

可以理解为:

配置 上游收到的路径
proxy_pass http://backend; 保留原始 URI
proxy_pass http://backend/; / 替换匹配到的普通前缀

正则 location、命名 location 和发生 URI 重写的场景限制更多,不建议依赖模糊的路径替换行为,应明确设计上下游路径契约。

8.2 常用代理请求头

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;

后端用途:

请求头 含义
Host 用户访问的域名
X-Real-IP 直接连接到 Nginx 的客户端 IP
X-Forwarded-For 经过多层代理后的 IP 链
X-Forwarded-Proto 原始请求协议,通常为 httphttps

后端只有在请求确实来自可信代理时,才应信任 X-Forwarded-*。应用直接暴露公网时,客户端可以伪造这些请求头。

8.3 WebSocket

map 应放在 http 上下文中:

nginx 复制代码
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

代理位置中增加:

nginx 复制代码
location /ws/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_pass http://quickly_fill_backend;
}

9. 常见业务场景模板

9.1 所有请求都交给后端

nginx 复制代码
location / {
    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_pass http://app_backend;
}

9.2 静态文件优先,不存在时交给后端

nginx 复制代码
location / {
    root /data/www/public;
    try_files $uri $uri/ @backend;
}

location @backend {
    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_pass http://app_backend;
}

9.3 独立资源目录

nginx 复制代码
location /assets/ {
    alias /data/releases/current/assets/;
    expires 30d;
}

静态文件不存在时,Nginx 的静态文件处理模块会直接返回 404,不必额外写 try_files。若需要多级回退,优先调整发布目录结构后改用 root,并用调试日志核对 $request_filename

9.4 上传文件下载

nginx 复制代码
location /uploads/ {
    alias /data/app/uploads/;

    # 不允许浏览目录列表。
    autoindex off;

    # 降低用户上传内容被浏览器当作页面执行的风险。
    add_header X-Content-Type-Options nosniff always;
}

用户上传目录不要允许执行脚本;上传文件名、扩展名、MIME 和访问权限仍应由应用层校验。

10. 容易踩坑的配置

10.1 全局扩展名正则覆盖 API

nginx 复制代码
location /api/ {
    proxy_pass http://app_backend;
}

location ~* \.pdf$ {
    root /data/www/public;
}

/api/report.pdf 可能被后面的正则规则处理。解决方法是把静态正则限定为 ^/site/,或在 API 前缀上使用 ^~

10.2 缺失的 JavaScript 返回 HTML

nginx 复制代码
location ~* \.js$ {
    try_files $uri /index.html;
}

这会导致浏览器出现类似错误:

text 复制代码
Refused to execute script because its MIME type ('text/html') is not executable.

静态资源规则应使用 try_files $uri =404;,SPA 的 HTML 回退放到页面路由规则中。

10.3 URI 尾部斜杠不一致

location /site/ 不匹配 /site。可增加精确跳转:

nginx 复制代码
location = /site {
    return 301 /site/;
}

10.4 忘记加载 MIME 类型

http 中通常需要:

nginx 复制代码
include mime.types;
default_type application/octet-stream;

否则 CSS、JavaScript、字体等资源可能返回不正确的 Content-Type

10.5 缓存 HTML 入口页

带内容哈希的静态资源可以长期缓存,但 index.html 一般应短缓存或不缓存,否则发布后用户可能继续引用旧资源:

nginx 复制代码
location = /site/index.html {
    root /data/www/htdocs/quickly_fill/page/.output/public;
    add_header Cache-Control "no-cache" always;
}

11. 检查、发布与排障

修改后先检查语法:

bash 复制代码
nginx -t

语法通过后平滑加载:

bash 复制代码
nginx -s reload

使用 systemd 的系统通常执行:

bash 复制代码
sudo systemctl reload nginx

查看最终合并后的完整配置:

bash 复制代码
nginx -T

验证请求:

bash 复制代码
curl -I http://example.com/site/assets/app.js
curl -I http://example.com/site/not-found.js
curl -i http://example.com/site/client-side-route
curl -i http://example.com/api/health

预期结果:

请求 预期
已存在的静态资源 200,且 Content-Type 正确
不存在的 .js/图片 404,不是 index.html
SPA 前端路由 200,返回 index.html
API 后端实际状态码和响应体

需要确认物理文件路径时,可临时增加包含 $request_filename 的日志格式:

nginx 复制代码
log_format location_debug '$remote_addr "$request" status=$status '
                          'uri=$uri file=$request_filename';
access_log /var/log/nginx/location_debug.log location_debug;

确认完成后应撤销临时高频调试日志,避免无意义的磁盘占用。

12. 针对原配置的结论

如果 /site/assets/app.js 对应的真实文件是:

text 复制代码
/data/www/htdocs/quickly_fill/page/.output/public/site/assets/app.js

建议改为:

nginx 复制代码
location ~* ^/site/.+\.(?:js|css|png|jpe?g|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|docx?|xlsx?|pptx?|pdf|txt|xml|webp)$ {
    root /data/www/htdocs/quickly_fill/page/.output/public;
    try_files $uri =404;
}

location /site/ {
    root /data/www/htdocs/quickly_fill/page/.output/public;
    try_files $uri $uri/ /site/index.html;
}

如果真实文件是:

text 复制代码
/data/www/htdocs/quickly_fill/page/.output/public/assets/app.js

也就是需要去掉 URI 中的 /site/,则使用带捕获组的正则 alias 处理静态资源,并将 SPA 页面回退分开配置:

nginx 复制代码
location ~* ^/site/(?<asset_path>.+\.(?:js|css|png|jpe?g|gif|swf|ico|bmp|avi|rm|rmvb|mp4|mp3|rar|zip|iso|docx?|xlsx?|pptx?|pdf|txt|xml|webp))$ {
    alias /data/www/htdocs/quickly_fill/page/.output/public/$asset_path;
}

location = /site/index.html {
    alias /data/www/htdocs/quickly_fill/page/.output/public/index.html;
    add_header Cache-Control "no-cache" always;
}

location /site/ {
    alias /data/www/htdocs/quickly_fill/page/.output/public/;
    error_page 404 =200 /site/index.html;
}

最终选择不能只看现有片段,必须同时确认两件事:浏览器请求的 URI,以及服务器上资源的真实绝对路径。

相关推荐
SkyWalking中文站8 小时前
SkyWalking 11 与 BanyanDB 0.11:在存储引擎内部实现 Trace 尾部采样
运维·监控·自动化运维
姚不倒9 小时前
etcd 学习系列(二):集群架构 —— 3 节点是如何工作的
运维·架构·etcd
pt104310 小时前
网络自动化Python课程:Cisco PyATS网络自动化测试框架
运维·自动化
小袁拒绝摆烂11 小时前
Jenkins部署经验
运维·jenkins
刚入门的大一新生11 小时前
Linux-进程控制
linux·运维·服务器·c++
上火的金鱼妹11 小时前
K8S基础组件作用和关系整理
linux·运维·服务器·kubernetes
Vcaker11 小时前
Linux学习25-harbor私有仓库部署
linux·运维·学习
醉颜凉12 小时前
网络安全必学:粘性MAC地址(Sticky MAC)原理与应用全解析
运维·服务器·网络·安全·web安全
不懂的浪漫12 小时前
ToDesk 连接 Linux 后分辨率过低的解决方法
linux·运维·数据库